aws / aws-cdk

The AWS Cloud Development Kit is a framework for defining cloud infrastructure in code
https://aws.amazon.com/cdk
Apache License 2.0
11.65k stars 3.91k forks source link

cloudwatch: Use of EC2 action with Multiple dimension set in metric results into error #29331

Closed rtejwani1309 closed 7 months ago

rtejwani1309 commented 8 months ago

Describe the bug

While trying to create a Custom Metric with multiple dimension, and adding EC2 action, the CDK synth fails with the error below.

/Users/Desktop/tscdk/node_modules/aws-cdk-lib/aws-cloudwatch/lib/alarm.js:1
"use strict";var _a;Object.defineProperty(exports,"__esModule",{value:!0}),exports.Alarm=exports.TreatMissingData=exports.ComparisonOperator=void 0;var jsiiDeprecationWarnings=()=>{var tmp=require("../../.warnings.jsii.js");return jsiiDeprecationWarnings=()=>tmp,tmp};const JSII_RTTI_SYMBOL_1=Symbol.for("jsii.rtti");var alarm_base_1=()=>{var tmp=require("./alarm-base");return alarm_base_1=()=>tmp,tmp},cloudwatch_generated_1=()=>{var tmp=require("./cloudwatch.generated");return cloudwatch_generated_1=()=>tmp,tmp},metric_util_1=()=>{var tmp=require("./private/metric-util");return metric_util_1=()=>tmp,tmp},object_1=()=>{var tmp=require("./private/object");return object_1=()=>tmp,tmp},rendering_1=()=>{var tmp=require("./private/rendering");return rendering_1=()=>tmp,tmp},statistic_1=()=>{var tmp=require("./private/statistic");return statistic_1=()=>tmp,tmp},core_1=()=>{var tmp=require("../../core");return core_1=()=>tmp,tmp},ComparisonOperator;(function(ComparisonOperator2){ComparisonOperator2.GREATER_THAN_OR_EQUAL_TO_THRESHOLD="GreaterThanOrEqualToThreshold",ComparisonOperator2.GREATER_THAN_THRESHOLD="GreaterThanThreshold",ComparisonOperator2.LESS_THAN_THRESHOLD="LessThanThreshold",ComparisonOperator2.LESS_THAN_OR_EQUAL_TO_THRESHOLD="LessThanOrEqualToThreshold",ComparisonOperator2.LESS_THAN_LOWER_OR_GREATER_THAN_UPPER_THRESHOLD="LessThanLowerOrGreaterThanUpperThreshold",ComparisonOperator2.GREATER_THAN_UPPER_THRESHOLD="GreaterThanUpperThreshold",ComparisonOperator2.LESS_THAN_LOWER_THRESHOLD="LessThanLowerThreshold"})(ComparisonOperator||(exports.ComparisonOperator=ComparisonOperator={}));const OPERATOR_SYMBOLS={GreaterThanOrEqualToThreshold:">=",GreaterThanThreshold:">",LessThanThreshold:"<",LessThanOrEqualToThreshold:"<="};var TreatMissingData;(function(TreatMissingData2){TreatMissingData2.BREACHING="breaching",TreatMissingData2.NOT_BREACHING="notBreaching",TreatMissingData2.IGNORE="ignore",TreatMissingData2.MISSING="missing"})(TreatMissingData||(exports.TreatMissingData=TreatMissingData={}));class Alarm extends alarm_base_1().AlarmBase{static fromAlarmName(scope,id,alarmName){const stack=core_1().Stack.of(scope);return this.fromAlarmArn(scope,id,stack.formatArn({service:"cloudwatch",resource:"alarm",resourceName:alarmName,arnFormat:core_1().ArnFormat.COLON_RESOURCE_NAME}))}static fromAlarmArn(scope,id,alarmArn){class Import extends alarm_base_1().AlarmBase{constructor(){super(...arguments),this.alarmArn=alarmArn,this.alarmName=core_1().Stack.of(scope).splitArn(alarmArn,core_1().ArnFormat.COLON_RESOURCE_NAME).resourceName}}return new Import(scope,id)}constructor(scope,id,props){super(scope,id,{physicalName:props.alarmName});try{jsiiDeprecationWarnings().aws_cdk_lib_aws_cloudwatch_AlarmProps(props)}catch(error){throw process.env.JSII_DEBUG!=="1"&&error.name==="DeprecationError"&&Error.captureStackTrace(error,Alarm),error}const comparisonOperator=props.comparisonOperator||ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD,metricProps=this.renderMetric(props.metric);props.period&&(metricProps.period=props.period.toSeconds()),props.statistic&&Object.assign(metricProps,{statistic:renderIfSimpleStatistic(props.statistic),extendedStatistic:renderIfExtendedStatistic(props.statistic)});const alarm=new(cloudwatch_generated_1()).CfnAlarm(this,"Resource",{alarmDescription:props.alarmDescription,alarmName:this.physicalName,comparisonOperator,threshold:props.threshold,datapointsToAlarm:props.datapointsToAlarm,evaluateLowSampleCountPercentile:props.evaluateLowSampleCountPercentile,evaluationPeriods:props.evaluationPeriods,treatMissingData:props.treatMissingData,actionsEnabled:props.actionsEnabled,alarmActions:core_1().Lazy.list({produce:()=>this.alarmActionArns}),insufficientDataActions:core_1().Lazy.list({produce:()=>this.insufficientDataActionArns}),okActions:core_1().Lazy.list({produce:()=>this.okActionArns}),...metricProps});this.alarmArn=this.getResourceArnAttribute(alarm.attrArn,{service:"cloudwatch",resource:"alarm",resourceName:this.physicalName,arnFormat:core_1().ArnFormat.COLON_RESOURCE_NAME}),this.alarmName=this.getResourceNameAttribute(alarm.ref),this.metric=props.metric;const datapoints=props.datapointsToAlarm||props.evaluationPeriods;this.annotation={label:`${this.metric} ${OPERATOR_SYMBOLS[comparisonOperator]} ${props.threshold} for ${datapoints} datapoints within ${describePeriod(props.evaluationPeriods*(0,metric_util_1().metricPeriod)(props.metric).toSeconds())}`,value:props.threshold};for(const[i,message]of Object.entries(this.metric.warningsV2??{}))core_1().Annotations.of(this).addWarningV2(i,message)}toAnnotation(){return this.annotation}addAlarmAction(...actions){try{jsiiDeprecationWarnings().aws_cdk_lib_aws_cloudwatch_IAlarmAction(actions)}catch(error){throw process.env.JSII_DEBUG!=="1"&&error.name==="DeprecationError"&&Error.captureStackTrace(error,this.addAlarmAction),error}this.alarmActionArns===void 0&&(this.alarmActionArns=[]),this.alarmActionArns.push(...actions.map(a=>this.validateActionArn(a.bind(this,this).alarmActionArn)))}validateActionArn(actionArn){if(/arn:aws[a-z0-9-]*:automate:[a-z|\d|-]+:ec2:[a-z]+/.test(actionArn)){const metricConfig=this.metric.toMetricConfig();if(metricConfig.metricStat?.dimensions?.length!=1||metricConfig.metricStat?.dimensions[0].name!="InstanceId")throw new Error(`EC2 alarm actions requires an EC2 Per-Instance Metric. (${JSON.stringify(metricConfig)} does not have an 'InstanceId' dimension)`)}return actionArn}renderMetric(metric){const self=this;return(0,metric_util_1().dispatchMetric)(metric,{withStat(stat,conf){return self.validateMetricStat(stat,metric),conf.renderingProperties?.label==null&&!self.requiresAccountId(stat)?(0,object_1().dropUndefined)({dimensions:stat.dimensions,namespace:stat.namespace,metricName:stat.metricName,period:stat.period?.toSeconds(),statistic:renderIfSimpleStatistic(stat.statistic),extendedStatistic:renderIfExtendedStatistic(stat.statistic),unit:stat.unitFilter}):{metrics:[{metricStat:{metric:{metricName:stat.metricName,namespace:stat.namespace,dimensions:stat.dimensions},period:stat.period.toSeconds(),stat:stat.statistic,unit:stat.unitFilter},id:"m1",accountId:self.requiresAccountId(stat)?stat.account:void 0,label:conf.renderingProperties?.label,returnData:!0}]}},withExpression(){const mset=new(rendering_1()).MetricSet;mset.addTopLevel(!0,metric);let eid=0;function uniqueMetricId(){return`expr_${++eid}`}return{metrics:mset.entries.map(entry=>(0,metric_util_1().dispatchMetric)(entry.metric,{withStat(stat,conf){return self.validateMetricStat(stat,entry.metric),{metricStat:{metric:{metricName:stat.metricName,namespace:stat.namespace,dimensions:stat.dimensions},period:stat.period.toSeconds(),stat:stat.statistic,unit:stat.unitFilter},id:entry.id||uniqueMetricId(),accountId:self.requiresAccountId(stat)?stat.account:void 0,label:conf.renderingProperties?.label,returnData:entry.tag?void 0:!1}},withExpression(expr,conf){const hasSubmetrics=mathExprHasSubmetrics(expr);return hasSubmetrics&&assertSubmetricsCount(expr),self.validateMetricExpression(expr),{expression:expr.expression,id:entry.id||uniqueMetricId(),label:conf.renderingProperties?.label,period:hasSubmetrics?void 0:expr.period,returnData:entry.tag?void 0:!1}}}))}}})}validateMetricStat(stat,metric){const stack=core_1().Stack.of(this);if(definitelyDifferent(stat.region,stack.region))throw new Error(`Cannot create an Alarm in region '${stack.region}' based on metric '${metric}' in '${stat.region}'`)}validateMetricExpression(expr){if(expr.searchAccount!==void 0||expr.searchRegion!==void 0)throw new Error("Cannot create an Alarm based on a MathExpression which specifies a searchAccount or searchRegion")}requiresAccountId(stat){const stackAccount=core_1().Stack.of(this).account;return stat.account===void 0?!1:stackAccount!==stat.account}}exports.Alarm=Alarm,_a=JSII_RTTI_SYMBOL_1,Alarm[_a]={fqn:"aws-cdk-lib.aws_cloudwatch.Alarm",version:"2.128.0"};function definitelyDifferent(x,y){return x&&!core_1().Token.isUnresolved(y)&&x!==y}function describePeriod(seconds){return seconds===60?"1 minute":seconds===1?"1 second":seconds>60?seconds/60+" minutes":seconds+" seconds"}function renderIfSimpleStatistic(statistic){if(statistic===void 0)return;const parsed=(0,statistic_1().parseStatistic)(statistic);if(parsed.type==="simple")return(0,statistic_1().normalizeStatistic)(parsed)}function renderIfExtendedStatistic(statistic){if(statistic===void 0)return;const parsed=(0,statistic_1().parseStatistic)(statistic);if(parsed.type!=="simple")return parsed.type==="single"||parsed.type==="pair"?(0,statistic_1().normalizeStatistic)(parsed):parsed.statistic}function mathExprHasSubmetrics(expr){return Object.keys(expr.usingMetrics).length>0}function assertSubmetricsCount(expr){if(Object.keys(expr.usingMetrics).length>10)throw new Error("Alarms on math expressions cannot contain more than 10 individual metrics")}
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   ^
Error: EC2 alarm actions requires an EC2 Per-Instance Metric. ({"metricStat":{"dimensions":[{"name":"ImageId","value":"ami-0e670eb768a5fc3d4"},{"name":"InstanceId","value":"i-080e122e0fe45205e"},{"name":"InstanceType","value":"t2.micro"},{"name":"device","value":"xvda1"},{"name":"fstype","value":"xfs"},{"name":"path","value":"/"}],"namespace":"CWAgent","metricName":"disk_used_percent","period":{"amount":5,"unit":{"label":"minutes","isoLabel":"M","inMillis":60000}},"statistic":"Average"},"renderingProperties":{}} does not have an 'InstanceId' dimension)
    at Alarm.validateActionArn (/Users//Desktop/tscdk/node_modules/aws-cdk-lib/aws-cloudwatch/lib/alarm.js:1:5300)
    at /Users//Desktop/tscdk/node_modules/aws-cdk-lib/aws-cloudwatch/lib/alarm.js:1:4982
    at Array.map (<anonymous>)
    at Alarm.addAlarmAction (/Users//Desktop/tscdk/node_modules/aws-cdk-lib/aws-cloudwatch/lib/alarm.js:1:4970)
    at new TscdkStack (/Users//Desktop/tscdk/lib/tscdk-stack.ts:66:15)
    at Object.<anonymous> (/Users//Desktop/tscdk/bin/tscdk.ts:7:1)
    at Module._compile (node:internal/modules/cjs/loader:1256:14)
    at Module.m._compile (/Users//Desktop/tscdk/node_modules/ts-node/src/index.ts:1618:23)
    at Module._extensions..js (node:internal/modules/cjs/loader:1310:10)
    at Object.require.extensions.<computed> [as .ts] (/Users//Desktop/tscdk/node_modules/ts-node/src/index.ts:1621:12)

Subprocess exited with error 1

The code for generating the error is as below

import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
// import * as sqs from 'aws-cdk-lib/aws-sqs';
import * as ec2 from 'aws-cdk-lib/aws-ec2'
import * as cw from 'aws-cdk-lib/aws-cloudwatch'
import { Ec2Action, Ec2InstanceAction } from 'aws-cdk-lib/aws-cloudwatch-actions';

export class TscdkStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const metric = new cw.Metric({
      namespace: 'CWAgent',
      metricName: 'disk_used_percent',
      dimensionsMap: {

        InstanceId: "<instance-id>",
        ImageId: "ami-id",
        InstanceType: "t2.micro",
        path: "/",
        device: "xvda1",
        fstype: "xfs"
      },
      period: cdk.Duration.minutes(5),
      statistic: "Average"
    })

    const sev3Alarm = new cw.Alarm(this, `DISK_USED_PERCENT_SEV3`, {
      alarmName: `DISK_USED_PERCENT_SEV3`,
      actionsEnabled: true,
      metric: metric,
      evaluationPeriods: 1,
      datapointsToAlarm: 1,
      threshold: 75,
      comparisonOperator: cw.ComparisonOperator.GREATER_THAN_THRESHOLD,
      treatMissingData: cw.TreatMissingData.BREACHING,
    });

    sev3Alarm.addAlarmAction(new Ec2Action(Ec2InstanceAction.REBOOT));
  }
}

To resolve the issue, there are 2 ways of doing it, the first one is we comment out the other dimensions apart from instance id in the dimensions map as below.

import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
// import * as sqs from 'aws-cdk-lib/aws-sqs';
import * as ec2 from 'aws-cdk-lib/aws-ec2'
import * as cw from 'aws-cdk-lib/aws-cloudwatch'
import { Ec2Action, Ec2InstanceAction } from 'aws-cdk-lib/aws-cloudwatch-actions';

export class TscdkStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const metric = new cw.Metric({
      namespace: 'CWAgent',
      metricName: 'disk_used_percent',
      dimensionsMap: {
        InstanceId: "instance-id"
      },
      period: cdk.Duration.minutes(5),
      statistic: "Average"
    })

    const sev3Alarm = new cw.Alarm(this, `DISK_USED_PERCENT_SEV3`, {
      alarmName: `DISK_USED_PERCENT_SEV3`,
      actionsEnabled: true,
      metric: metric,
      evaluationPeriods: 1,
      datapointsToAlarm: 1,
      threshold: 75,
      comparisonOperator: cw.ComparisonOperator.GREATER_THAN_THRESHOLD,
      treatMissingData: cw.TreatMissingData.BREACHING,
    });

    sev3Alarm.addAlarmAction(new Ec2Action(Ec2InstanceAction.REBOOT));
  }
}

Or we use escape hatch as below.

import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
// import * as sqs from 'aws-cdk-lib/aws-sqs';
import * as ec2 from 'aws-cdk-lib/aws-ec2'
import * as cw from 'aws-cdk-lib/aws-cloudwatch'
import { Ec2Action, Ec2InstanceAction } from 'aws-cdk-lib/aws-cloudwatch-actions';

export class TscdkStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const metric = new cw.Metric({
      namespace: 'CWAgent',
      metricName: 'disk_used_percent',
      dimensionsMap: {

        InstanceId: "instance-id"
      },
      period: cdk.Duration.minutes(5),
      statistic: "Average"
    })

    const sev3Alarm = new cw.Alarm(this, `DISK_USED_PERCENT_SEV3`, {
      alarmName: `DISK_USED_PERCENT_SEV3`,
      actionsEnabled: true,
      metric: metric,
      evaluationPeriods: 1,
      datapointsToAlarm: 1,
      threshold: 75,
      comparisonOperator: cw.ComparisonOperator.GREATER_THAN_THRESHOLD,
      treatMissingData: cw.TreatMissingData.BREACHING,
    });
    var sev3AlarmCfn = sev3Alarm.node.defaultChild as cw.CfnAlarm
    sev3AlarmCfn.dimensions = [{
        name: "InstanceId",
        value: "instance-id"
      },
       {
        name: "ImageId",
        value: "imageId"
      },                                                          
      {
        name: "device",
        value: "xvda1"
      },
      {
        name: "InstanceType",
        value: "t2.micro"
      },
      {
        name: "path",
        value: "/"
      },
      {
        name: "device",
        value: "xvda1"
      },
      {
        name: "fstype",
        value: "xfs"
      }

    ]

    sev3Alarm.addAlarmAction(new Ec2Action(Ec2InstanceAction.REBOOT));
  }
}

Expected Behavior

The expected behaviour is that it should allow to add EC2 action for the custom metrics too, as one can do using CloudWatch Console, and CloudFormation.

Current Behavior

The current behaviour is that it results into the error stated below.

/Users//Desktop/tscdk/node_modules/aws-cdk-lib/aws-cloudwatch/lib/alarm.js:1
"use strict";var _a;Object.defineProperty(exports,"__esModule",{value:!0}),exports.Alarm=exports.TreatMissingData=exports.ComparisonOperator=void 0;var jsiiDeprecationWarnings=()=>{var tmp=require("../../.warnings.jsii.js");return jsiiDeprecationWarnings=()=>tmp,tmp};const JSII_RTTI_SYMBOL_1=Symbol.for("jsii.rtti");var alarm_base_1=()=>{var tmp=require("./alarm-base");return alarm_base_1=()=>tmp,tmp},cloudwatch_generated_1=()=>{var tmp=require("./cloudwatch.generated");return cloudwatch_generated_1=()=>tmp,tmp},metric_util_1=()=>{var tmp=require("./private/metric-util");return metric_util_1=()=>tmp,tmp},object_1=()=>{var tmp=require("./private/object");return object_1=()=>tmp,tmp},rendering_1=()=>{var tmp=require("./private/rendering");return rendering_1=()=>tmp,tmp},statistic_1=()=>{var tmp=require("./private/statistic");return statistic_1=()=>tmp,tmp},core_1=()=>{var tmp=require("../../core");return core_1=()=>tmp,tmp},ComparisonOperator;(function(ComparisonOperator2){ComparisonOperator2.GREATER_THAN_OR_EQUAL_TO_THRESHOLD="GreaterThanOrEqualToThreshold",ComparisonOperator2.GREATER_THAN_THRESHOLD="GreaterThanThreshold",ComparisonOperator2.LESS_THAN_THRESHOLD="LessThanThreshold",ComparisonOperator2.LESS_THAN_OR_EQUAL_TO_THRESHOLD="LessThanOrEqualToThreshold",ComparisonOperator2.LESS_THAN_LOWER_OR_GREATER_THAN_UPPER_THRESHOLD="LessThanLowerOrGreaterThanUpperThreshold",ComparisonOperator2.GREATER_THAN_UPPER_THRESHOLD="GreaterThanUpperThreshold",ComparisonOperator2.LESS_THAN_LOWER_THRESHOLD="LessThanLowerThreshold"})(ComparisonOperator||(exports.ComparisonOperator=ComparisonOperator={}));const OPERATOR_SYMBOLS={GreaterThanOrEqualToThreshold:">=",GreaterThanThreshold:">",LessThanThreshold:"<",LessThanOrEqualToThreshold:"<="};var TreatMissingData;(function(TreatMissingData2){TreatMissingData2.BREACHING="breaching",TreatMissingData2.NOT_BREACHING="notBreaching",TreatMissingData2.IGNORE="ignore",TreatMissingData2.MISSING="missing"})(TreatMissingData||(exports.TreatMissingData=TreatMissingData={}));class Alarm extends alarm_base_1().AlarmBase{static fromAlarmName(scope,id,alarmName){const stack=core_1().Stack.of(scope);return this.fromAlarmArn(scope,id,stack.formatArn({service:"cloudwatch",resource:"alarm",resourceName:alarmName,arnFormat:core_1().ArnFormat.COLON_RESOURCE_NAME}))}static fromAlarmArn(scope,id,alarmArn){class Import extends alarm_base_1().AlarmBase{constructor(){super(...arguments),this.alarmArn=alarmArn,this.alarmName=core_1().Stack.of(scope).splitArn(alarmArn,core_1().ArnFormat.COLON_RESOURCE_NAME).resourceName}}return new Import(scope,id)}constructor(scope,id,props){super(scope,id,{physicalName:props.alarmName});try{jsiiDeprecationWarnings().aws_cdk_lib_aws_cloudwatch_AlarmProps(props)}catch(error){throw process.env.JSII_DEBUG!=="1"&&error.name==="DeprecationError"&&Error.captureStackTrace(error,Alarm),error}const comparisonOperator=props.comparisonOperator||ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD,metricProps=this.renderMetric(props.metric);props.period&&(metricProps.period=props.period.toSeconds()),props.statistic&&Object.assign(metricProps,{statistic:renderIfSimpleStatistic(props.statistic),extendedStatistic:renderIfExtendedStatistic(props.statistic)});const alarm=new(cloudwatch_generated_1()).CfnAlarm(this,"Resource",{alarmDescription:props.alarmDescription,alarmName:this.physicalName,comparisonOperator,threshold:props.threshold,datapointsToAlarm:props.datapointsToAlarm,evaluateLowSampleCountPercentile:props.evaluateLowSampleCountPercentile,evaluationPeriods:props.evaluationPeriods,treatMissingData:props.treatMissingData,actionsEnabled:props.actionsEnabled,alarmActions:core_1().Lazy.list({produce:()=>this.alarmActionArns}),insufficientDataActions:core_1().Lazy.list({produce:()=>this.insufficientDataActionArns}),okActions:core_1().Lazy.list({produce:()=>this.okActionArns}),...metricProps});this.alarmArn=this.getResourceArnAttribute(alarm.attrArn,{service:"cloudwatch",resource:"alarm",resourceName:this.physicalName,arnFormat:core_1().ArnFormat.COLON_RESOURCE_NAME}),this.alarmName=this.getResourceNameAttribute(alarm.ref),this.metric=props.metric;const datapoints=props.datapointsToAlarm||props.evaluationPeriods;this.annotation={label:`${this.metric} ${OPERATOR_SYMBOLS[comparisonOperator]} ${props.threshold} for ${datapoints} datapoints within ${describePeriod(props.evaluationPeriods*(0,metric_util_1().metricPeriod)(props.metric).toSeconds())}`,value:props.threshold};for(const[i,message]of Object.entries(this.metric.warningsV2??{}))core_1().Annotations.of(this).addWarningV2(i,message)}toAnnotation(){return this.annotation}addAlarmAction(...actions){try{jsiiDeprecationWarnings().aws_cdk_lib_aws_cloudwatch_IAlarmAction(actions)}catch(error){throw process.env.JSII_DEBUG!=="1"&&error.name==="DeprecationError"&&Error.captureStackTrace(error,this.addAlarmAction),error}this.alarmActionArns===void 0&&(this.alarmActionArns=[]),this.alarmActionArns.push(...actions.map(a=>this.validateActionArn(a.bind(this,this).alarmActionArn)))}validateActionArn(actionArn){if(/arn:aws[a-z0-9-]*:automate:[a-z|\d|-]+:ec2:[a-z]+/.test(actionArn)){const metricConfig=this.metric.toMetricConfig();if(metricConfig.metricStat?.dimensions?.length!=1||metricConfig.metricStat?.dimensions[0].name!="InstanceId")throw new Error(`EC2 alarm actions requires an EC2 Per-Instance Metric. (${JSON.stringify(metricConfig)} does not have an 'InstanceId' dimension)`)}return actionArn}renderMetric(metric){const self=this;return(0,metric_util_1().dispatchMetric)(metric,{withStat(stat,conf){return self.validateMetricStat(stat,metric),conf.renderingProperties?.label==null&&!self.requiresAccountId(stat)?(0,object_1().dropUndefined)({dimensions:stat.dimensions,namespace:stat.namespace,metricName:stat.metricName,period:stat.period?.toSeconds(),statistic:renderIfSimpleStatistic(stat.statistic),extendedStatistic:renderIfExtendedStatistic(stat.statistic),unit:stat.unitFilter}):{metrics:[{metricStat:{metric:{metricName:stat.metricName,namespace:stat.namespace,dimensions:stat.dimensions},period:stat.period.toSeconds(),stat:stat.statistic,unit:stat.unitFilter},id:"m1",accountId:self.requiresAccountId(stat)?stat.account:void 0,label:conf.renderingProperties?.label,returnData:!0}]}},withExpression(){const mset=new(rendering_1()).MetricSet;mset.addTopLevel(!0,metric);let eid=0;function uniqueMetricId(){return`expr_${++eid}`}return{metrics:mset.entries.map(entry=>(0,metric_util_1().dispatchMetric)(entry.metric,{withStat(stat,conf){return self.validateMetricStat(stat,entry.metric),{metricStat:{metric:{metricName:stat.metricName,namespace:stat.namespace,dimensions:stat.dimensions},period:stat.period.toSeconds(),stat:stat.statistic,unit:stat.unitFilter},id:entry.id||uniqueMetricId(),accountId:self.requiresAccountId(stat)?stat.account:void 0,label:conf.renderingProperties?.label,returnData:entry.tag?void 0:!1}},withExpression(expr,conf){const hasSubmetrics=mathExprHasSubmetrics(expr);return hasSubmetrics&&assertSubmetricsCount(expr),self.validateMetricExpression(expr),{expression:expr.expression,id:entry.id||uniqueMetricId(),label:conf.renderingProperties?.label,period:hasSubmetrics?void 0:expr.period,returnData:entry.tag?void 0:!1}}}))}}})}validateMetricStat(stat,metric){const stack=core_1().Stack.of(this);if(definitelyDifferent(stat.region,stack.region))throw new Error(`Cannot create an Alarm in region '${stack.region}' based on metric '${metric}' in '${stat.region}'`)}validateMetricExpression(expr){if(expr.searchAccount!==void 0||expr.searchRegion!==void 0)throw new Error("Cannot create an Alarm based on a MathExpression which specifies a searchAccount or searchRegion")}requiresAccountId(stat){const stackAccount=core_1().Stack.of(this).account;return stat.account===void 0?!1:stackAccount!==stat.account}}exports.Alarm=Alarm,_a=JSII_RTTI_SYMBOL_1,Alarm[_a]={fqn:"aws-cdk-lib.aws_cloudwatch.Alarm",version:"2.128.0"};function definitelyDifferent(x,y){return x&&!core_1().Token.isUnresolved(y)&&x!==y}function describePeriod(seconds){return seconds===60?"1 minute":seconds===1?"1 second":seconds>60?seconds/60+" minutes":seconds+" seconds"}function renderIfSimpleStatistic(statistic){if(statistic===void 0)return;const parsed=(0,statistic_1().parseStatistic)(statistic);if(parsed.type==="simple")return(0,statistic_1().normalizeStatistic)(parsed)}function renderIfExtendedStatistic(statistic){if(statistic===void 0)return;const parsed=(0,statistic_1().parseStatistic)(statistic);if(parsed.type!=="simple")return parsed.type==="single"||parsed.type==="pair"?(0,statistic_1().normalizeStatistic)(parsed):parsed.statistic}function mathExprHasSubmetrics(expr){return Object.keys(expr.usingMetrics).length>0}function assertSubmetricsCount(expr){if(Object.keys(expr.usingMetrics).length>10)throw new Error("Alarms on math expressions cannot contain more than 10 individual metrics")}
                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   ^
Error: EC2 alarm actions requires an EC2 Per-Instance Metric. ({"metricStat":{"dimensions":[{"name":"ImageId","value":"ami-0e670eb768a5fc3d4"},{"name":"InstanceId","value":"i-080e122e0fe45205e"},{"name":"InstanceType","value":"t2.micro"},{"name":"device","value":"xvda1"},{"name":"fstype","value":"xfs"},{"name":"path","value":"/"}],"namespace":"CWAgent","metricName":"disk_used_percent","period":{"amount":5,"unit":{"label":"minutes","isoLabel":"M","inMillis":60000}},"statistic":"Average"},"renderingProperties":{}} does not have an 'InstanceId' dimension)
    at Alarm.validateActionArn (/Users//Desktop/tscdk/node_modules/aws-cdk-lib/aws-cloudwatch/lib/alarm.js:1:5300)
    at /Users//Desktop/tscdk/node_modules/aws-cdk-lib/aws-cloudwatch/lib/alarm.js:1:4982
    at Array.map (<anonymous>)
    at Alarm.addAlarmAction (/Users//Desktop/tscdk/node_modules/aws-cdk-lib/aws-cloudwatch/lib/alarm.js:1:4970)
    at new TscdkStack (/Users//Desktop/tscdk/lib/tscdk-stack.ts:66:15)
    at Object.<anonymous> (/Users//Desktop/tscdk/bin/tscdk.ts:7:1)
    at Module._compile (node:internal/modules/cjs/loader:1256:14)
    at Module.m._compile (/Users//Desktop/tscdk/node_modules/ts-node/src/index.ts:1618:23)
    at Module._extensions..js (node:internal/modules/cjs/loader:1310:10)
    at Object.require.extensions.<computed> [as .ts] (/Users//Desktop/tscdk/node_modules/ts-node/src/index.ts:1621:12)

Subprocess exited with error 1

Reproduction Steps

Use the below code, and run cdk synth

import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
// import * as sqs from 'aws-cdk-lib/aws-sqs';
import * as ec2 from 'aws-cdk-lib/aws-ec2'
import * as cw from 'aws-cdk-lib/aws-cloudwatch'
import { Ec2Action, Ec2InstanceAction } from 'aws-cdk-lib/aws-cloudwatch-actions';

export class TscdkStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const metric = new cw.Metric({
      namespace: 'CWAgent',
      metricName: 'disk_used_percent',
      dimensionsMap: {

        InstanceId: "instance-id",
        ImageId: "image-id",
        InstanceType: "t2.micro",
        path: "/",
        device: "xvda1",
        fstype: "xfs"
      },
      period: cdk.Duration.minutes(5),
      statistic: "Average"
    })

    const sev3Alarm = new cw.Alarm(this, `DISK_USED_PERCENT_SEV3`, {
      alarmName: `DISK_USED_PERCENT_SEV3`,
      actionsEnabled: true,
      metric: metric,
      evaluationPeriods: 1,
      datapointsToAlarm: 1,
      threshold: 75,
      comparisonOperator: cw.ComparisonOperator.GREATER_THAN_THRESHOLD,
      treatMissingData: cw.TreatMissingData.BREACHING,
    });

    sev3Alarm.addAlarmAction(new Ec2Action(Ec2InstanceAction.REBOOT));
  }
}

### Possible Solution

Possible bug, it is causing the issue here at this piece of code.

https://github.com/aws/aws-cdk/blob/9487b39d6da4ab7efd884c3a8379b03e317786b9/packages/%40aws-cdk/aws-cloudwatch/lib/alarm.ts#L249



### Additional Information/Context

_No response_

### CDK CLI Version

"aws-cdk-lib": "2.128.0",

### Framework Version

"constructs": "^10.0.0",

### Node.js Version

v18.18.0

### OS

MacOs

### Language

TypeScript

### Language Version

"typescript": "~5.3.3"

### Other information

Please note, I see a workflow issue over here, as if adding the dimensions directly to the metric is not working, then it should also not work while adding it through escape hatch.
pahud commented 8 months ago

Looks like this error is from:

https://github.com/aws/aws-cdk/blob/8b01f45f751df558683934ab69e7836b3900a2cb/packages/aws-cdk-lib/aws-cloudwatch/lib/alarm.ts#L265-L275

I don't have the immediate fix off the top of my head. Making it a p1 bug.

GavinZZ commented 8 months ago

Maybe I'm missing some step but I cannot reproduce this issue. I ran cdk synth using the following example as you provided

import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as cw from 'aws-cdk-lib/aws-cloudwatch'
import { Ec2Action, Ec2InstanceAction } from 'aws-cdk-lib/aws-cloudwatch-actions';

export class UnlinkedCdkAppStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const metric = new cw.Metric({
      namespace: 'CWAgent',
      metricName: 'disk_used_percent',
      dimensionsMap: {

        InstanceId: "instance-id",
        ImageId: "ami-id",
        InstanceType: "t2.micro",
        path: "/",
        device: "xvda1",
        fstype: "xfs"
      },
      period: cdk.Duration.minutes(5),
      statistic: "Average"
    })

    const sev3Alarm = new cw.Alarm(this, `DISK_USED_PERCENT_SEV3`, {
      alarmName: `DISK_USED_PERCENT_SEV3`,
      actionsEnabled: true,
      metric: metric,
      evaluationPeriods: 1,
      datapointsToAlarm: 1,
      threshold: 75,
      comparisonOperator: cw.ComparisonOperator.GREATER_THAN_THRESHOLD,
      treatMissingData: cw.TreatMissingData.BREACHING,
    });

    sev3Alarm.addAlarmAction(new Ec2Action(Ec2InstanceAction.REBOOT));
  }
}

cdk synth correct synth the code into the following template

Resources:
  DISKUSEDPERCENTSEV36CE6B660:
    Type: AWS::CloudWatch::Alarm
    Properties:
      ActionsEnabled: true
      AlarmActions:
        - Fn::Join:
            - ""
            - - "arn:"
              - Ref: AWS::Partition
              - ":automate:"
              - Ref: AWS::Region
              - :ec2:reboot
      AlarmName: DISK_USED_PERCENT_SEV3
      ComparisonOperator: GreaterThanThreshold
      DatapointsToAlarm: 1
      Dimensions:
        - Name: ImageId
          Value: ami-id
        - Name: InstanceId
          Value: instance-id
        - Name: InstanceType
          Value: t2.micro
        - Name: device
          Value: xvda1
        - Name: fstype
          Value: xfs
        - Name: path
          Value: /
      EvaluationPeriods: 1
      MetricName: disk_used_percent
      Namespace: CWAgent
      Period: 300
      Statistic: Average
      Threshold: 75
      TreatMissingData: breaching
    Metadata:
      aws:cdk:path: UnlinkedCdkAppStack/DISK_USED_PERCENT_SEV3/Resource
  CDKMetadata:
    Type: AWS::CDK::Metadata
    Properties:
      Analytics: v2:deflate64:H4sIAAAAAAAA/yXIPQ6DMAxA4bOwJ4ZkYEfcAA5QpU6qGoIt5acMiLu30Ol9ehaM7aFr3J41+lVHesIxF4er+q0HRql+dwXfcAzRpU2NL75xXppClpowXB6FPRUSPhWLD7Dk9mN6sB2YZslEOlUutAWY/v0C8ssPDnoAAAA=
    Metadata:
      aws:cdk:path: UnlinkedCdkAppStack/CDKMetadata/Default
    Condition: CDKMetadataAvailable
Conditions:
  CDKMetadataAvailable:
    Fn::Or:
      - Fn::Or:
          - Fn::Equals:
              - Ref: AWS::Region
              - af-south-1
          - Fn::Equals:
              - Ref: AWS::Region
              - ap-east-1
          - Fn::Equals:
              - Ref: AWS::Region
              - ap-northeast-1
          - Fn::Equals:
              - Ref: AWS::Region
              - ap-northeast-2
          - Fn::Equals:
              - Ref: AWS::Region
              - ap-south-1
          - Fn::Equals:
              - Ref: AWS::Region
              - ap-southeast-1
          - Fn::Equals:
              - Ref: AWS::Region
              - ap-southeast-2
          - Fn::Equals:
              - Ref: AWS::Region
              - ca-central-1
          - Fn::Equals:
              - Ref: AWS::Region
              - cn-north-1
          - Fn::Equals:
              - Ref: AWS::Region
              - cn-northwest-1
      - Fn::Or:
          - Fn::Equals:
              - Ref: AWS::Region
              - eu-central-1
          - Fn::Equals:
              - Ref: AWS::Region
              - eu-north-1
          - Fn::Equals:
              - Ref: AWS::Region
              - eu-south-1
          - Fn::Equals:
              - Ref: AWS::Region
              - eu-west-1
          - Fn::Equals:
              - Ref: AWS::Region
              - eu-west-2
          - Fn::Equals:
              - Ref: AWS::Region
              - eu-west-3
          - Fn::Equals:
              - Ref: AWS::Region
              - il-central-1
          - Fn::Equals:
              - Ref: AWS::Region
              - me-central-1
          - Fn::Equals:
              - Ref: AWS::Region
              - me-south-1
          - Fn::Equals:
              - Ref: AWS::Region
              - sa-east-1
      - Fn::Or:
          - Fn::Equals:
              - Ref: AWS::Region
              - us-east-1
          - Fn::Equals:
              - Ref: AWS::Region
              - us-east-2
          - Fn::Equals:
              - Ref: AWS::Region
              - us-west-1
          - Fn::Equals:
              - Ref: AWS::Region
              - us-west-2
Parameters:
  BootstrapVersion:
    Type: AWS::SSM::Parameter::Value<String>
    Default: /cdk-bootstrap/hnb659fds/version
    Description: Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]
Rules:
  CheckBootstrapVersion:
    Assertions:
      - Assert:
          Fn::Not:
            - Fn::Contains:
                - - "1"
                  - "2"
                  - "3"
                  - "4"
                  - "5"
                - Ref: BootstrapVersion
        AssertDescription: CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI.
pahud commented 8 months ago

Hi @GavinZZ

I can reproduce it. Check out my full cdk app.

#!/usr/bin/env node
import 'source-map-support/register';
import { App, StackProps, Stack, CfnOutput, Duration,
  aws_cloudwatch as cw,
  aws_cloudwatch_actions as cwactions,
} from 'aws-cdk-lib';

import { Construct } from 'constructs';

export class UnlinkedCdkAppStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    const metric = new cw.Metric({
      namespace: 'CWAgent',
      metricName: 'disk_used_percent',
      dimensionsMap: {

        InstanceId: "instance-id",
        ImageId: "ami-id",
        InstanceType: "t2.micro",
        path: "/",
        device: "xvda1",
        fstype: "xfs"
      },
      period: Duration.minutes(5),
      statistic: "Average"
    })

    const sev3Alarm = new cw.Alarm(this, `DISK_USED_PERCENT_SEV3`, {
      alarmName: `DISK_USED_PERCENT_SEV3`,
      actionsEnabled: true,
      metric: metric,
      evaluationPeriods: 1,
      datapointsToAlarm: 1,
      threshold: 75,
      comparisonOperator: cw.ComparisonOperator.GREATER_THAN_THRESHOLD,
      treatMissingData: cw.TreatMissingData.BREACHING,
    });

    sev3Alarm.addAlarmAction(new cwactions.Ec2Action(cwactions.Ec2InstanceAction.REBOOT));
  }
}

const app = new App();
const env = { region: process.env.CDK_DEFAULT_REGION, account: process.env.CDK_DEFAULT_ACCOUNT };
new UnlinkedCdkAppStack(app, 'UnlinkedCdkAppStack', { env });

cdk synth returns this error:

Error: EC2 alarm actions requires an EC2 Per-Instance Metric. ({"metricStat":{"dimensions":[{"name":"ImageId","value":"ami-id"},{"name":"InstanceId","value":"instance-id"},{"name":"InstanceType","value":"t2.micro"},{"name":"device","value":"xvda1"},{"name":"fstype","value":"xfs"},{"name":"path","value":"/"}],"namespace":"CWAgent","metricName":"disk_used_percent","period":{"amount":5,"unit":{"label":"minutes","isoLabel":"M","inMillis":60000}},"statistic":"Average"},"renderingProperties":{}} does not have an 'InstanceId' dimension)
    at Alarm.validateActionArn (/Users/hunhsieh/repos/xxx/node_modules/aws-cdk-lib/aws-cloudwatch/lib/alarm.js:1:5300)
    at /Users/hunhsieh/repos/xxx/node_modules/aws-cdk-lib/aws-cloudwatch/lib/alarm.js:1:4982
    at Array.map (<anonymous>)
    at Alarm.addAlarmAction (/Users/hunhsieh/repos/xxx/node_modules/aws-cdk-lib/aws-cloudwatch/lib/alarm.js:1:4970)
    at new UnlinkedCdkAppStack (/Users/hunhsieh/repos/xxx/bin/xxx.ts:41:15)
    at Object.<anonymous> (/Users/hunhsieh/repos/xxx/bin/xxx.ts:47:1)
    at Module._compile (node:internal/modules/cjs/loader:1254:14)
    at Module.m._compile (/Users/hunhsieh/repos/xxx/node_modules/ts-node/src/index.ts:1618:23)
    at Module._extensions..js (node:internal/modules/cjs/loader:1308:10)
    at Object.require.extensions.<computed> [as .ts] (/Users/hunhsieh/repos/xxx/node_modules/ts-node/src/index.ts:1621:12)

Subprocess exited with error 1

% npx cdk --version 2.131.0 (build 92b912d)

GavinZZ commented 8 months ago

Thanks Pahud, I can confirm that this is reproducible. Will investigate this issue.

github-actions[bot] commented 7 months ago

⚠️COMMENT VISIBILITY WARNING⚠️

Comments on closed issues are hard for our team to see. If you need more assistance, please either tag a team member or open a new issue that references this one. If you wish to keep having a conversation with other community members under this issue feel free to do so.

github-actions[bot] commented 7 months ago

⚠️COMMENT VISIBILITY WARNING⚠️

Comments on closed issues are hard for our team to see. If you need more assistance, please either tag a team member or open a new issue that references this one. If you wish to keep having a conversation with other community members under this issue feel free to do so.