├── .eslintrc.json ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.yml │ ├── config.yml │ └── feature_request.yml ├── pull_request_template.md └── workflows │ ├── auto-approve.yml │ ├── build.yml │ ├── pull-request-lint.yml │ ├── release.yml │ └── upgrade-main.yml ├── .gitignore ├── .mergify.yml ├── .npmignore ├── .prettierignore ├── .prettierrc.json ├── .projen ├── deps.json ├── files.json └── tasks.json ├── .projenrc.ts ├── .vscode ├── launch.json └── settings.json ├── API.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── NOTICE ├── README.md ├── SECURITY.md ├── assets ├── BitmapWidgetRenderingSupport │ └── index.js └── SecretsManagerMetricsPublisher │ └── index.js ├── lib ├── common │ ├── alarm │ │ ├── AlarmFactory.ts │ │ ├── AlarmNamingStrategy.ts │ │ ├── CustomAlarmThreshold.ts │ │ ├── IAlarmAnnotationStrategy.ts │ │ ├── IAlarmDedupeStringProcessor.ts │ │ ├── IAlarmNamingStrategy.ts │ │ ├── ScaleAlarms.ts │ │ ├── action │ │ │ ├── IAlarmActionStrategy.ts │ │ │ ├── LambdaAlarmActionStrategy.ts │ │ │ ├── MultipleAlarmActionStrategy.ts │ │ │ ├── NoopAlarmActionStrategy.ts │ │ │ ├── OpsItemAlarmActionStrategy.ts │ │ │ ├── SnsAlarmActionStrategy.ts │ │ │ └── index.ts │ │ ├── index.ts │ │ └── metric-adjuster │ │ │ ├── CompositeMetricAdjuster.ts │ │ │ ├── DefaultMetricAdjuster.ts │ │ │ ├── IMetricAdjuster.ts │ │ │ ├── Route53HealthCheckMetricAdjuster.ts │ │ │ └── index.ts │ ├── index.ts │ ├── metric │ │ ├── AnomalyDetectionMathExpression.ts │ │ ├── BaseMetricFactory.ts │ │ ├── MetricFactory.ts │ │ ├── MetricStatistic.ts │ │ ├── MetricWithAlarmSupport.ts │ │ ├── RateComputationMethod.ts │ │ └── index.ts │ ├── monitoring │ │ ├── Monitoring.ts │ │ ├── MonitoringScope.ts │ │ ├── alarms │ │ │ ├── AgeAlarmFactory.ts │ │ │ ├── AnomalyDetectingAlarmFactory.ts │ │ │ ├── AuroraAlarmFactory.ts │ │ │ ├── ConnectionAlarmFactory.ts │ │ │ ├── CustomAlarmFactory.ts │ │ │ ├── DynamoAlarmFactory.ts │ │ │ ├── ElastiCacheAlarmFactory.ts │ │ │ ├── ErrorAlarmFactory.ts │ │ │ ├── KinesisAlarmFactory.ts │ │ │ ├── KinesisDataAnalyticsAlarmFactory.ts │ │ │ ├── LatencyAlarmFactory.ts │ │ │ ├── LogLevelAlarmFactory.ts │ │ │ ├── OpenSearchClusterAlarmFactory.ts │ │ │ ├── QueueAlarmFactory.ts │ │ │ ├── SecretsManagerAlarmFactory.ts │ │ │ ├── TaskHealthAlarmFactory.ts │ │ │ ├── ThroughputAlarmFactory.ts │ │ │ ├── TopicAlarmFactory.ts │ │ │ ├── TpsAlarmFactory.ts │ │ │ ├── UsageAlarmFactory.ts │ │ │ └── index.ts │ │ └── index.ts │ ├── strings.ts │ ├── url │ │ ├── AwsConsoleUrlFactory.ts │ │ └── index.ts │ └── widget │ │ ├── axis.ts │ │ ├── color.ts │ │ ├── index.ts │ │ ├── size.ts │ │ └── types.ts ├── dashboard │ ├── BitmapDashboard.ts │ ├── DashboardRenderingPreference.ts │ ├── DashboardSegment.ts │ ├── DashboardWithBitmapCopy.ts │ ├── DefaultDashboardFactory.ts │ ├── DefaultWidgetFactory.ts │ ├── DynamicDashboardFactory.ts │ ├── DynamicDashboardSegment.ts │ ├── IDashboardFactory.ts │ ├── IDynamicDashboardFactory.ts │ ├── IWidgetFactory.ts │ ├── MonitoringNamingStrategy.ts │ ├── SingleWidgetDashboardSegment.ts │ ├── index.ts │ └── widget │ │ ├── AlarmMatrixWidget.ts │ │ ├── BitmapWidget.ts │ │ ├── CustomWidget.ts │ │ ├── HeaderWidget.ts │ │ ├── KeyValueTableWidget.ts │ │ ├── KeyValueTableWidgetV2.ts │ │ ├── MonitoringHeaderWidget.ts │ │ ├── StrictGraphWidget.ts │ │ ├── UnofficialWidgets.ts │ │ └── index.ts ├── facade │ ├── IMonitoringAspect.ts │ ├── MonitoringAspect.ts │ ├── MonitoringFacade.ts │ └── index.ts ├── index.ts └── monitoring │ ├── aws-acm │ ├── CertificateManagerMetricFactory.ts │ ├── CertificateManagerMonitoring.ts │ └── index.ts │ ├── aws-apigateway │ ├── ApiGatewayMetricFactory.ts │ ├── ApiGatewayMonitoring.ts │ └── index.ts │ ├── aws-apigatewayv2 │ ├── ApiGatewayV2HttpApiMetricFactory.ts │ ├── ApiGatewayV2HttpApiMonitoring.ts │ └── index.ts │ ├── aws-appsync │ ├── AppSyncMetricFactory.ts │ ├── AppSyncMonitoring.ts │ └── index.ts │ ├── aws-billing │ ├── BillingMetricFactory.ts │ ├── BillingMonitoring.ts │ └── index.ts │ ├── aws-cloudfront │ ├── CloudFrontDistributionMetricFactory.ts │ ├── CloudFrontDistributionMonitoring.ts │ └── index.ts │ ├── aws-cloudwatch │ ├── CloudWatchLogsMetricFactory.ts │ ├── LogMonitoring.ts │ └── index.ts │ ├── aws-codebuild │ ├── CodeBuildProjectMetricFactory.ts │ ├── CodeBuildProjectMonitoring.ts │ └── index.ts │ ├── aws-docdb │ ├── DocumentDbMetricFactory.ts │ ├── DocumentDbMonitoring.ts │ └── index.ts │ ├── aws-dynamo │ ├── DynamoTableGlobalSecondaryIndexMetricFactory.ts │ ├── DynamoTableGlobalSecondaryIndexMonitoring.ts │ ├── DynamoTableMetricFactory.ts │ ├── DynamoTableMonitoring.ts │ └── index.ts │ ├── aws-ec2 │ ├── AutoScalingGroupMetricFactory.ts │ ├── AutoScalingGroupMonitoring.ts │ ├── EC2MetricFactory.ts │ ├── EC2Monitoring.ts │ └── index.ts │ ├── aws-ecs-patterns │ ├── BaseServiceMetricFactory.ts │ ├── Ec2ServiceMonitoring.ts │ ├── FargateServiceMonitoring.ts │ ├── index.ts │ └── misc.ts │ ├── aws-elasticache │ ├── ElastiCacheClusterMetricFactory.ts │ ├── ElastiCacheClusterMonitoring.ts │ └── index.ts │ ├── aws-glue │ ├── GlueJobMetricFactory.ts │ ├── GlueJobMonitoring.ts │ └── index.ts │ ├── aws-kinesis │ ├── KinesisDataStreamMetricFactory.ts │ ├── KinesisDataStreamMonitoring.ts │ ├── KinesisFirehoseMetricFactory.ts │ ├── KinesisFirehoseMonitoring.ts │ └── index.ts │ ├── aws-kinesisanalytics │ ├── KinesisDataAnalyticsMetricFactory.ts │ ├── KinesisDataAnalyticsMonitoring.ts │ └── index.ts │ ├── aws-lambda │ ├── LambdaFunctionEnhancedMetricFactory.ts │ ├── LambdaFunctionMetricFactory.ts │ ├── LambdaFunctionMonitoring.ts │ └── index.ts │ ├── aws-loadbalancing │ ├── ApplicationLoadBalancerMetricFactory.ts │ ├── LoadBalancerMetricFactory.ts │ ├── NetworkLoadBalancerMetricFactory.ts │ ├── NetworkLoadBalancerMonitoring.ts │ └── index.ts │ ├── aws-opensearch │ ├── OpenSearchBackportedMetrics.ts │ ├── OpenSearchClusterMetricFactory.ts │ ├── OpenSearchClusterMonitoring.ts │ └── index.ts │ ├── aws-rds │ ├── AuroraClusterMonitoring.ts │ ├── RdsClusterMetricFactory.ts │ ├── RdsClusterMonitoring.ts │ ├── RdsInstanceMetricFactory.ts │ ├── RdsInstanceMonitoring.ts │ └── index.ts │ ├── aws-redshift │ ├── RedshiftClusterMetricFactory.ts │ ├── RedshiftClusterMonitoring.ts │ └── index.ts │ ├── aws-s3 │ ├── S3BucketMetricFactory.ts │ ├── S3BucketMonitoring.ts │ └── index.ts │ ├── aws-secretsmanager │ ├── SecretsManagerMetricFactory.ts │ ├── SecretsManagerMetricsPublisher.ts │ ├── SecretsManagerMonitoring.ts │ ├── SecretsManagerSecretMetricFactory.ts │ ├── SecretsManagerSecretMonitoring.ts │ └── index.ts │ ├── aws-sns │ ├── SnsTopicMetricFactory.ts │ ├── SnsTopicMonitoring.ts │ └── index.ts │ ├── aws-sqs │ ├── SqsQueueMetricFactory.ts │ ├── SqsQueueMonitoring.ts │ ├── SqsQueueMonitoringWithDlq.ts │ └── index.ts │ ├── aws-step-functions │ ├── StepFunctionActivityMetricFactory.ts │ ├── StepFunctionActivityMonitoring.ts │ ├── StepFunctionLambdaIntegrationMetricFactory.ts │ ├── StepFunctionLambdaIntegrationMonitoring.ts │ ├── StepFunctionMetricFactory.ts │ ├── StepFunctionMonitoring.ts │ ├── StepFunctionServiceIntegrationMetricFactory.ts │ ├── StepFunctionServiceIntegrationMonitoring.ts │ └── index.ts │ ├── aws-synthetics │ ├── SyntheticsCanaryMetricFactory.ts │ ├── SyntheticsCanaryMonitoring.ts │ └── index.ts │ ├── aws-wafv2 │ ├── WafV2MetricFactory.ts │ ├── WafV2Monitoring.ts │ └── index.ts │ ├── custom │ ├── CustomMonitoring.ts │ └── index.ts │ ├── fluentbit │ ├── FluentBitConstants.ts │ ├── FluentBitMetricFactory.ts │ ├── FluentBitMonitoring.ts │ └── index.ts │ └── index.ts ├── package.json ├── test ├── assets │ └── schema.graphql ├── common │ ├── alarm │ │ ├── AlarmFactory.test.ts │ │ ├── AlarmNamingStrategy.test.ts │ │ ├── LatencyAlarmFactory.test.ts │ │ ├── LatencyType.test.ts │ │ ├── ScaleAlarms.test.ts │ │ ├── __snapshots__ │ │ │ ├── AlarmFactory.test.ts.snap │ │ │ └── ScaleAlarms.test.ts.snap │ │ ├── action │ │ │ ├── LambdaAlarmActionStrategy.test.ts │ │ │ ├── MultipleAlarmActionStrategy.test.ts │ │ │ ├── OpsItemAlarmActionStrategy.test.ts │ │ │ ├── SnsAlarmActionStrategy.test.ts │ │ │ ├── __snapshots__ │ │ │ │ ├── LambdaAlarmActionStrategy.test.ts.snap │ │ │ │ ├── MultipleAlarmActionStrategy.test.ts.snap │ │ │ │ ├── OpsItemAlarmActionStrategy.test.ts.snap │ │ │ │ └── SnsAlarmActionStrategy.test.ts.snap │ │ │ └── actions.test.ts │ │ └── metric-adjuster │ │ │ ├── CompositeMetricAdjuster.test.ts │ │ │ ├── DefaultMetricAdjuster.test.ts │ │ │ └── Route53HealthCheckMetricAdjuster.test.ts │ ├── metric │ │ ├── MetricFactory.test.ts │ │ └── __snapshots__ │ │ │ └── MetricFactory.test.ts.snap │ ├── strings.test.ts │ ├── url │ │ └── AwsConsoleUrlFactory.test.ts │ └── widget │ │ └── size.test.ts ├── dashboard │ ├── BitmapDashboard.test.ts │ ├── DashboardWithBitmapCopy.test.ts │ ├── DefaultDashboardFactory.test.ts │ ├── DynamicDashboardFactory.test.ts │ ├── MonitoringNamingStrategy.test.ts │ ├── __snapshots__ │ │ └── DefaultDashboardFactory.test.ts.snap │ └── widget │ │ ├── AlarmMatrixWidget.test.ts │ │ ├── BitmapWidget.test.ts │ │ ├── CustomWidget.test.ts │ │ ├── HeaderWidget.test.ts │ │ ├── KeyValueTableWidget.test.ts │ │ ├── KeyValueTableWidgetV2.test.ts │ │ ├── MonitoringHeaderWidget.test.ts │ │ ├── StrictGraphWidget.test.ts │ │ ├── UnofficialWidgets.test.ts │ │ └── __snapshots__ │ │ ├── BitmapWidget.test.ts.snap │ │ ├── HeaderWidget.test.ts.snap │ │ ├── KeyValueTableWidget.test.ts.snap │ │ ├── KeyValueTableWidgetV2.test.ts.snap │ │ ├── MonitoringHeaderWidget.test.ts.snap │ │ ├── StrictGraphWidget.test.ts.snap │ │ └── UnofficialWidgets.test.ts.snap ├── facade │ ├── MonitoringAspect.test.ts │ ├── MonitoringFacade.test.ts │ └── __snapshots__ │ │ ├── MonitoringAspect.test.ts.snap │ │ └── MonitoringFacade.test.ts.snap ├── monitoring │ ├── TestMonitoringScope.ts │ ├── aws-acm │ │ ├── CertificateManagerMonitoring.test.ts │ │ └── __snapshots__ │ │ │ └── CertificateManagerMonitoring.test.ts.snap │ ├── aws-apigateway │ │ ├── ApiGatewayMonitoring.test.ts │ │ └── __snapshots__ │ │ │ └── ApiGatewayMonitoring.test.ts.snap │ ├── aws-apigatewayv2 │ │ ├── ApiGatewayV2HttpApiMonitoring.test.ts │ │ └── __snapshots__ │ │ │ └── ApiGatewayV2HttpApiMonitoring.test.ts.snap │ ├── aws-appsync │ │ ├── AppSyncMonitoring.test.ts │ │ └── __snapshots__ │ │ │ └── AppSyncMonitoring.test.ts.snap │ ├── aws-billing │ │ ├── BillingMonitoring.test.ts │ │ └── __snapshots__ │ │ │ └── BillingMonitoring.test.ts.snap │ ├── aws-cloudfront │ │ ├── CloudFrontDistribution.test.ts │ │ └── __snapshots__ │ │ │ └── CloudFrontDistribution.test.ts.snap │ ├── aws-cloudwatch │ │ ├── LogMonitoring.test.ts │ │ └── __snapshots__ │ │ │ └── LogMonitoring.test.ts.snap │ ├── aws-codebuild │ │ ├── CodeBuildProjectMonitoring.test.ts │ │ └── __snapshots__ │ │ │ └── CodeBuildProjectMonitoring.test.ts.snap │ ├── aws-docdb │ │ ├── DocumentDbMonitoring.test.ts │ │ └── __snapshots__ │ │ │ └── DocumentDbMonitoring.test.ts.snap │ ├── aws-dynamo │ │ ├── DynamoTableGlobalSecondaryIndexMonitoring.test.ts │ │ ├── DynamoTableMonitoring.test.ts │ │ └── __snapshots__ │ │ │ ├── DynamoTableGlobalSecondaryIndexMonitoring.test.ts.snap │ │ │ └── DynamoTableMonitoring.test.ts.snap │ ├── aws-ec2 │ │ ├── AutoScalingGroupMonitoring.test.ts │ │ ├── EC2Monitoring.test.ts │ │ └── __snapshots__ │ │ │ ├── AutoScalingGroupMonitoring.test.ts.snap │ │ │ └── EC2Monitoring.test.ts.snap │ ├── aws-ecs-patterns │ │ ├── Ec2ServiceMonitoring.test.ts │ │ ├── FargateServiceMonitoring.test.ts │ │ ├── __snapshots__ │ │ │ ├── Ec2ServiceMonitoring.test.ts.snap │ │ │ ├── FargateServiceMonitoring.test.ts.snap │ │ │ └── misc.test.ts.snap │ │ └── misc.test.ts │ ├── aws-elasticache │ │ ├── ElastiCacheClusterMonitoring.test.ts │ │ └── __snapshots__ │ │ │ └── ElastiCacheClusterMonitoring.test.ts.snap │ ├── aws-glue │ │ ├── GlueJobMonitoring.test.ts │ │ └── __snapshots__ │ │ │ └── GlueJobMonitoring.test.ts.snap │ ├── aws-kinesis │ │ ├── KinesisDataStreamMonitoring.test.ts │ │ ├── KinesisFirehoseMonitoring.test.ts │ │ └── __snapshots__ │ │ │ ├── KinesisDataStreamMonitoring.test.ts.snap │ │ │ └── KinesisFirehoseMonitoring.test.ts.snap │ ├── aws-kinesisanalytics │ │ ├── KinesisDataAnalyticsMonitoring.test.ts │ │ └── __snapshots__ │ │ │ └── KinesisDataAnalyticsMonitoring.test.ts.snap │ ├── aws-lambda │ │ ├── LambdaFunctionMonitoring.test.ts │ │ └── __snapshots__ │ │ │ └── LambdaFunctionMonitoring.test.ts.snap │ ├── aws-loadbalancing │ │ ├── ApplicationLoadBalancerMonitoring.test.ts │ │ ├── NetworkLoadBalancerMonitoring.test.ts │ │ └── __snapshots__ │ │ │ ├── ApplicationLoadBalancerMonitoring.test.ts.snap │ │ │ └── NetworkLoadBalancerMonitoring.test.ts.snap │ ├── aws-opensearch │ │ ├── OpenSearchClusterMonitoring.test.ts │ │ └── __snapshots__ │ │ │ └── OpenSearchClusterMonitoring.test.ts.snap │ ├── aws-rds │ │ ├── AuroraClusterMonitoring.test.ts │ │ ├── RdsClusterMonitoring.test.ts │ │ ├── RdsInstanceMonitoring.test.ts │ │ └── __snapshots__ │ │ │ ├── AuroraClusterMonitoring.test.ts.snap │ │ │ ├── RdsClusterMonitoring.test.ts.snap │ │ │ └── RdsInstanceMonitoring.test.ts.snap │ ├── aws-redshift │ │ ├── RedshiftClusterMonitoring.test.ts │ │ └── __snapshots__ │ │ │ └── RedshiftClusterMonitoring.test.ts.snap │ ├── aws-s3 │ │ ├── S3BucketMonitoring.test.ts │ │ └── __snapshots__ │ │ │ └── S3BucketMonitoring.test.ts.snap │ ├── aws-secretsmanager │ │ ├── SecretsManagerMonitoring.test.ts │ │ ├── SecretsManagerSecretMonitoring.test.ts │ │ └── __snapshots__ │ │ │ ├── SecretsManagerMonitoring.test.ts.snap │ │ │ └── SecretsManagerSecretMonitoring.test.ts.snap │ ├── aws-sns │ │ ├── SnsTopicMonitoring.test.ts │ │ └── __snapshots__ │ │ │ └── SnsTopicMonitoring.test.ts.snap │ ├── aws-sqs │ │ ├── SqsQueueMonitoring.test.ts │ │ ├── SqsQueueMonitoringWithDlq.test.ts │ │ └── __snapshots__ │ │ │ ├── SqsQueueMonitoring.test.ts.snap │ │ │ └── SqsQueueMonitoringWithDlq.test.ts.snap │ ├── aws-step-functions │ │ ├── StepFunctionActivityMonitoring.test.ts │ │ ├── StepFunctionLambdaIntegrationMonitoring.test.ts │ │ ├── StepFunctionMonitoring.test.ts │ │ ├── StepFunctionServiceIntegrationMonitoring.test.ts │ │ └── __snapshots__ │ │ │ ├── StepFunctionActivityMonitoring.test.ts.snap │ │ │ ├── StepFunctionLambdaIntegrationMonitoring.test.ts.snap │ │ │ ├── StepFunctionMonitoring.test.ts.snap │ │ │ └── StepFunctionServiceIntegrationMonitoring.test.ts.snap │ ├── aws-synthetics │ │ ├── SyntheticsCanaryMonitoring.test.ts │ │ └── __snapshots__ │ │ │ └── SyntheticsCanaryMonitoring.test.ts.snap │ ├── aws-wafv2 │ │ ├── WafV2Monitoring.test.ts │ │ └── __snapshots__ │ │ │ └── WafV2Monitoring.test.ts.snap │ ├── custom │ │ ├── CustomMonitoring.test.ts │ │ └── __snapshots__ │ │ │ └── CustomMonitoring.test.ts.snap │ └── fluentbit │ │ ├── FluentBitMonitoring.test.ts │ │ └── __snapshots__ │ │ └── FluentBitMonitoring.test.ts.snap ├── setup │ └── setup.ts └── utils │ └── SnapshotUtil.ts ├── tsconfig.dev.json └── yarn.lock /.gitattributes: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | 3 | * text=auto eol=lf 4 | *.snap linguist-generated 5 | /.eslintrc.json linguist-generated 6 | /.gitattributes linguist-generated 7 | /.github/pull_request_template.md linguist-generated 8 | /.github/workflows/auto-approve.yml linguist-generated 9 | /.github/workflows/build.yml linguist-generated 10 | /.github/workflows/pull-request-lint.yml linguist-generated 11 | /.github/workflows/release.yml linguist-generated 12 | /.github/workflows/upgrade-main.yml linguist-generated 13 | /.gitignore linguist-generated 14 | /.mergify.yml linguist-generated 15 | /.npmignore linguist-generated 16 | /.prettierignore linguist-generated 17 | /.prettierrc.json linguist-generated 18 | /.projen/** linguist-generated 19 | /.projen/deps.json linguist-generated 20 | /.projen/files.json linguist-generated 21 | /.projen/tasks.json linguist-generated 22 | /API.md linguist-generated 23 | /LICENSE linguist-generated 24 | /package.json linguist-generated 25 | /tsconfig.dev.json linguist-generated 26 | /yarn.lock linguist-generated -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: Bug report 2 | description: Report an issue 3 | labels: [bug] 4 | body: 5 | 6 | - type: input 7 | id: bug-version 8 | attributes: 9 | label: Version 10 | description: The version of cdk-monitoring-constructs you're using. 11 | placeholder: | 12 | Example: "v0.0.20" 13 | validations: 14 | required: true 15 | 16 | - type: textarea 17 | id: bug-reproduce-steps 18 | attributes: 19 | label: Steps and/or minimal code example to reproduce 20 | description: Provide an example of the issue. 21 | placeholder: | 22 | Example: 23 | 1. First step 24 | 2. Second step 25 | 3. Issue here 26 | validations: 27 | required: true 28 | 29 | - type: textarea 30 | id: bug-expected-behavior 31 | attributes: 32 | label: Expected behavior 33 | description: Explain what you expect to happen. 34 | placeholder: | 35 | Example: 36 | "This should happen..." 37 | validations: 38 | required: true 39 | 40 | - type: textarea 41 | id: bug-actual-behavior 42 | attributes: 43 | label: Actual behavior 44 | description: Explain what actually happens. 45 | placeholder: | 46 | Example: 47 | "This happened instead..." 48 | validations: 49 | required: true 50 | 51 | - type: textarea 52 | id: bug-other-details 53 | attributes: 54 | label: Other details 55 | placeholder: | 56 | Additional details and attachments. 57 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: true 2 | contact_links: 3 | - name: Report a security vulnerability 4 | url: http://aws.amazon.com/security/vulnerability-reporting/ 5 | about: Please report security vulnerabilities to AWS/Amazon Security instead of on GitHub. 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.yml: -------------------------------------------------------------------------------- 1 | name: Feature request 2 | description: Suggest a new feature or enhancement 3 | labels: [feature-request] 4 | body: 5 | 6 | - type: input 7 | id: feature-scope 8 | attributes: 9 | label: Feature scope 10 | description: The relevant metric namespace. 11 | placeholder: | 12 | Example: "DynamoDB" 13 | validations: 14 | required: true 15 | 16 | - type: textarea 17 | id: feature-description 18 | attributes: 19 | label: Describe your suggested feature 20 | description: What new feature or enhancement should be added? 21 | validations: 22 | required: true 23 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | Fixes # 2 | 3 | --- 4 | 5 | _By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license_ -------------------------------------------------------------------------------- /.github/workflows/auto-approve.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | 3 | name: auto-approve 4 | on: 5 | pull_request_target: 6 | types: 7 | - labeled 8 | - opened 9 | - synchronize 10 | - reopened 11 | - ready_for_review 12 | jobs: 13 | approve: 14 | runs-on: ubuntu-latest 15 | permissions: 16 | pull-requests: write 17 | if: contains(github.event.pull_request.labels.*.name, 'auto-approve') && (github.event.pull_request.user.login == 'cdklabs-automation') 18 | steps: 19 | - uses: hmarr/auto-approve-action@v2.2.1 20 | with: 21 | github-token: ${{ secrets.GITHUB_TOKEN }} 22 | -------------------------------------------------------------------------------- /.github/workflows/pull-request-lint.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | 3 | name: pull-request-lint 4 | on: 5 | pull_request_target: 6 | types: 7 | - labeled 8 | - opened 9 | - synchronize 10 | - reopened 11 | - ready_for_review 12 | - edited 13 | merge_group: {} 14 | jobs: 15 | validate: 16 | name: Validate PR title 17 | runs-on: ubuntu-latest 18 | permissions: 19 | pull-requests: write 20 | if: (github.event_name == 'pull_request' || github.event_name == 'pull_request_target') 21 | steps: 22 | - uses: amannn/action-semantic-pull-request@v5.4.0 23 | env: 24 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 25 | with: 26 | types: |- 27 | feat 28 | fix 29 | chore 30 | requireScope: false 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | !/.gitattributes 3 | !/.projen/tasks.json 4 | !/.projen/deps.json 5 | !/.projen/files.json 6 | !/.github/workflows/pull-request-lint.yml 7 | !/.github/workflows/auto-approve.yml 8 | !/package.json 9 | !/LICENSE 10 | !/.npmignore 11 | logs 12 | *.log 13 | npm-debug.log* 14 | yarn-debug.log* 15 | yarn-error.log* 16 | lerna-debug.log* 17 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 18 | pids 19 | *.pid 20 | *.seed 21 | *.pid.lock 22 | lib-cov 23 | coverage 24 | *.lcov 25 | .nyc_output 26 | build/Release 27 | node_modules/ 28 | jspm_packages/ 29 | *.tsbuildinfo 30 | .eslintcache 31 | *.tgz 32 | .yarn-integrity 33 | .cache 34 | /test-reports/ 35 | junit.xml 36 | /coverage/ 37 | !/.github/workflows/build.yml 38 | /dist/changelog.md 39 | /dist/version.txt 40 | !/.github/workflows/release.yml 41 | !/.mergify.yml 42 | !/.github/workflows/upgrade-main.yml 43 | !/.github/pull_request_template.md 44 | !/.prettierignore 45 | !/.prettierrc.json 46 | !/test/ 47 | !/tsconfig.dev.json 48 | !/lib/ 49 | /lib/**/*.js 50 | /lib/**/*.d.ts 51 | /lib/**/*.d.ts.map 52 | /dist/ 53 | !/.eslintrc.json 54 | .jsii 55 | tsconfig.json 56 | !/API.md 57 | !/.projenrc.ts 58 | -------------------------------------------------------------------------------- /.mergify.yml: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | 3 | queue_rules: 4 | - name: default 5 | update_method: merge 6 | conditions: 7 | - "#approved-reviews-by>=1" 8 | - -label~=(do-not-merge) 9 | - status-success=build 10 | - status-success=package-js 11 | - status-success=package-java 12 | - status-success=package-python 13 | - status-success=package-dotnet 14 | merge_method: squash 15 | commit_message_template: |- 16 | {{ title }} (#{{ number }}) 17 | 18 | {{ body }} 19 | pull_request_rules: 20 | - name: Automatic merge on approval and successful build 21 | actions: 22 | delete_head_branch: {} 23 | queue: 24 | name: default 25 | conditions: 26 | - "#approved-reviews-by>=1" 27 | - -label~=(do-not-merge) 28 | - status-success=build 29 | - status-success=package-js 30 | - status-success=package-java 31 | - status-success=package-python 32 | - status-success=package-dotnet 33 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | /.projen/ 3 | /test-reports/ 4 | junit.xml 5 | /coverage/ 6 | permissions-backup.acl 7 | /dist/changelog.md 8 | /dist/version.txt 9 | /.mergify.yml 10 | /.prettierignore 11 | /.prettierrc.json 12 | /test/ 13 | /tsconfig.dev.json 14 | !/lib/ 15 | !/lib/**/*.js 16 | !/lib/**/*.d.ts 17 | dist 18 | /tsconfig.json 19 | /.github/ 20 | /.vscode/ 21 | /.idea/ 22 | /.projenrc.js 23 | tsconfig.tsbuildinfo 24 | /.eslintrc.json 25 | !.jsii 26 | *.ts 27 | !*.d.ts 28 | /.gitattributes 29 | /.projenrc.ts 30 | /projenrc 31 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "all", 3 | "overrides": [] 4 | } 5 | -------------------------------------------------------------------------------- /.projen/deps.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": [ 3 | { 4 | "name": "@types/jest", 5 | "type": "build" 6 | }, 7 | { 8 | "name": "@types/node", 9 | "type": "build" 10 | }, 11 | { 12 | "name": "@typescript-eslint/eslint-plugin", 13 | "version": "^8", 14 | "type": "build" 15 | }, 16 | { 17 | "name": "@typescript-eslint/parser", 18 | "version": "^8", 19 | "type": "build" 20 | }, 21 | { 22 | "name": "commit-and-tag-version", 23 | "version": "^12", 24 | "type": "build" 25 | }, 26 | { 27 | "name": "eslint-config-prettier", 28 | "type": "build" 29 | }, 30 | { 31 | "name": "eslint-import-resolver-typescript", 32 | "type": "build" 33 | }, 34 | { 35 | "name": "eslint-plugin-import", 36 | "type": "build" 37 | }, 38 | { 39 | "name": "eslint-plugin-prettier", 40 | "type": "build" 41 | }, 42 | { 43 | "name": "eslint", 44 | "version": "^9", 45 | "type": "build" 46 | }, 47 | { 48 | "name": "jest", 49 | "type": "build" 50 | }, 51 | { 52 | "name": "jest-junit", 53 | "version": "^15", 54 | "type": "build" 55 | }, 56 | { 57 | "name": "jsii-diff", 58 | "type": "build" 59 | }, 60 | { 61 | "name": "jsii-docgen", 62 | "version": "^10.5.0", 63 | "type": "build" 64 | }, 65 | { 66 | "name": "jsii-pacmak", 67 | "type": "build" 68 | }, 69 | { 70 | "name": "jsii-rosetta", 71 | "version": "1.x", 72 | "type": "build" 73 | }, 74 | { 75 | "name": "jsii", 76 | "version": "1.x", 77 | "type": "build" 78 | }, 79 | { 80 | "name": "prettier", 81 | "type": "build" 82 | }, 83 | { 84 | "name": "projen", 85 | "type": "build" 86 | }, 87 | { 88 | "name": "ts-jest", 89 | "type": "build" 90 | }, 91 | { 92 | "name": "ts-node", 93 | "type": "build" 94 | }, 95 | { 96 | "name": "typescript", 97 | "type": "build" 98 | }, 99 | { 100 | "name": "@aws-cdk/aws-redshift-alpha", 101 | "version": "2.160.0-alpha.0", 102 | "type": "devenv" 103 | }, 104 | { 105 | "name": "aws-cdk-lib", 106 | "version": "^2.160.0", 107 | "type": "peer" 108 | }, 109 | { 110 | "name": "constructs", 111 | "version": "^10.0.5", 112 | "type": "peer" 113 | } 114 | ], 115 | "//": "~~ Generated by projen. To modify, edit .projenrc.ts and run \"npx projen\"." 116 | } 117 | -------------------------------------------------------------------------------- /.projen/files.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | ".eslintrc.json", 4 | ".gitattributes", 5 | ".github/pull_request_template.md", 6 | ".github/workflows/auto-approve.yml", 7 | ".github/workflows/build.yml", 8 | ".github/workflows/pull-request-lint.yml", 9 | ".github/workflows/release.yml", 10 | ".github/workflows/upgrade-main.yml", 11 | ".gitignore", 12 | ".mergify.yml", 13 | ".prettierignore", 14 | ".prettierrc.json", 15 | ".projen/deps.json", 16 | ".projen/files.json", 17 | ".projen/tasks.json", 18 | "LICENSE", 19 | "tsconfig.dev.json" 20 | ], 21 | "//": "~~ Generated by projen. To modify, edit .projenrc.ts and run \"npx projen\"." 22 | } 23 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "1.0.0", 3 | "configurations": [ 4 | { 5 | "type": "node", 6 | "request": "launch", 7 | "name": "Jest: current file", 8 | "program": "${workspaceFolder}/node_modules/.bin/jest", 9 | "args": [ 10 | "${fileBasenameNoExtension}" 11 | ], 12 | "console": "integratedTerminal" 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.codeActionsOnSave": { 3 | "source.fixAll.eslint": "explicit" 4 | }, 5 | "eslint.validate": ["javascript", "typescript"] 6 | } 7 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | We only provide support for the latest version of the library. 6 | 7 | ## Reporting a Vulnerability 8 | 9 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). 10 | 11 | Please do **not** create a public GitHub issue. 12 | -------------------------------------------------------------------------------- /assets/BitmapWidgetRenderingSupport/index.js: -------------------------------------------------------------------------------- 1 | const { CloudWatchClient, GetMetricWidgetImageCommand } = require('@aws-sdk/client-cloudwatch'); 2 | 3 | const DOCS = ` 4 | ## Display a CloudWatch bitmap graph 5 | Displays CloudWatch metrics as a bitmap, for faster display of metrics. 6 | 7 | ### Widget parameters 8 | Param | Description 9 | ---|--- 10 | **graph** | The graph definition. Use the parameters from the **Source** tab in CloudWatch Console's **Metrics** page. 11 | 12 | ### Example parameters 13 | \`\`\` yaml 14 | graph: 15 | view: timeSeries 16 | metrics: 17 | - [ AWS/Lambda, Invocations ] 18 | region: ${process.env.AWS_REGION} 19 | \`\`\` 20 | `; 21 | 22 | exports.handler = async (event) => { 23 | async function renderUsingCloudWatch(graph, width, height) { 24 | const client = new CloudWatchClient({ 25 | signingRegion: graph.region, 26 | retryMode: 'standard', 27 | }); 28 | const command = new GetMetricWidgetImageCommand({ 29 | MetricWidget: JSON.stringify(graph), 30 | }); 31 | const response = await client.send(command); 32 | const base64Image = Buffer.from(response.MetricWidgetImage).toString('base64'); 33 | return ``; 34 | } 35 | 36 | if (event.describe) { 37 | return DOCS; 38 | } 39 | 40 | const widgetContext = event.widgetContext; 41 | const timeRange = widgetContext.timeRange.zoom || widgetContext.timeRange; 42 | const start = new Date(timeRange.start).toISOString(); 43 | const end = new Date(timeRange.end).toISOString(); 44 | const width = widgetContext.width; 45 | const height = widgetContext.height; 46 | const graph = Object.assign(event.graph, {start, end, width, height}); 47 | 48 | return renderUsingCloudWatch(graph, width, height); 49 | }; 50 | -------------------------------------------------------------------------------- /assets/SecretsManagerMetricsPublisher/index.js: -------------------------------------------------------------------------------- 1 | const { CloudWatchClient, PutMetricDataCommand } = require('@aws-sdk/client-cloudwatch'); 2 | const { SecretsManagerClient, DescribeSecretCommand } = require('@aws-sdk/client-secrets-manager'); 3 | 4 | const region = process.env.AWS_REGION; 5 | const millisPerDay = 1000 * 60 * 60 * 24; 6 | 7 | const clientOptions = { 8 | signingRegion: region, 9 | retryMode: 'standard', 10 | }; 11 | 12 | const cloudwatchClient = new CloudWatchClient(clientOptions); 13 | const secretsManagerClient = new SecretsManagerClient(clientOptions); 14 | 15 | function daysSince(date, now = Date.now()) { 16 | const millis = now - date.getTime(); 17 | const days = millis / millisPerDay; 18 | return Math.floor(days); 19 | } 20 | 21 | exports.handler = async (event, context) => { 22 | console.info(`Retrieving secret for event ${JSON.stringify(event)}`); 23 | console.debug(`context: ${JSON.stringify(context)}`); 24 | 25 | const secret = await secretsManagerClient.send( 26 | new DescribeSecretCommand({ 27 | SecretId: event.secretId, 28 | }) 29 | ); 30 | 31 | console.debug(`Found secret: ${JSON.stringify(secret)}`); 32 | if (!secret.Name || !secret.CreatedDate) { 33 | throw new Error("Invalid secret response"); 34 | } 35 | 36 | // use retrieved secret name in case secretId was an arn 37 | const secretName = secret.Name; 38 | const lastChangedDate = secret.LastChangedDate ?? secret.CreatedDate; 39 | const lastRotatedDate = secret.LastRotatedDate ?? secret.CreatedDate; 40 | const now = Date.now(); 41 | 42 | const params = { 43 | Namespace: "SecretsManager", 44 | MetricData: [ 45 | { 46 | MetricName: "DaysSinceLastChange", 47 | Dimensions: [ 48 | { 49 | Name: "SecretName", 50 | Value: secretName, 51 | }, 52 | ], 53 | Unit: "Count", 54 | Value: daysSince(lastChangedDate, now), 55 | }, 56 | { 57 | MetricName: "DaysSinceLastRotation", 58 | Dimensions: [ 59 | { 60 | Name: "SecretName", 61 | Value: secretName, 62 | }, 63 | ], 64 | Unit: "Count", 65 | Value: daysSince(lastRotatedDate, now), 66 | }, 67 | ], 68 | }; 69 | console.debug(`putMetricData params: ${JSON.stringify(params)}`); 70 | console.info(`Publishing metrics for secret "${event.secretId}"`); 71 | const command = new PutMetricDataCommand(params); 72 | await cloudwatchClient.send(command); 73 | 74 | return Promise.resolve(); 75 | }; 76 | -------------------------------------------------------------------------------- /lib/common/alarm/IAlarmDedupeStringProcessor.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Strategy used to finalize dedupe string. 3 | */ 4 | export interface IAlarmDedupeStringProcessor { 5 | /** 6 | * Process the dedupe string which was specified by the user as an override. 7 | * 8 | * @param dedupeString 9 | * @return final dedupe string 10 | */ 11 | processDedupeStringOverride(dedupeString: string): string; 12 | 13 | /** 14 | * Process the dedupe string which was auto-generated. 15 | * 16 | * @param dedupeString 17 | * @return final dedupe string 18 | */ 19 | processDedupeString(dedupeString: string): string; 20 | } 21 | 22 | /** 23 | * Dedupe string processor that adds prefix and/or suffix to the dedupe string. 24 | */ 25 | export class ExtendDedupeString implements IAlarmDedupeStringProcessor { 26 | private readonly prefix: string; 27 | private readonly suffix: string; 28 | 29 | constructor(prefix?: string, suffix?: string) { 30 | this.prefix = prefix ?? ""; 31 | this.suffix = suffix ?? ""; 32 | } 33 | 34 | processDedupeString(dedupeString: string): string { 35 | return this.prefix + dedupeString + this.suffix; 36 | } 37 | 38 | processDedupeStringOverride(dedupeString: string): string { 39 | return this.prefix + dedupeString + this.suffix; 40 | } 41 | } 42 | 43 | /** 44 | * Default dedupe strategy - does not add any prefix nor suffix. 45 | */ 46 | export class DoNotModifyDedupeString extends ExtendDedupeString { 47 | // default constructor = no prefix, no suffix 48 | } 49 | -------------------------------------------------------------------------------- /lib/common/alarm/IAlarmNamingStrategy.ts: -------------------------------------------------------------------------------- 1 | import { IAlarmActionStrategy } from "./action"; 2 | 3 | export interface AlarmNamingInput { 4 | // TODO: make this required 5 | readonly action?: IAlarmActionStrategy; 6 | readonly alarmNameSuffix: string; 7 | readonly alarmNameOverride?: string; 8 | readonly alarmDedupeStringSuffix?: string; 9 | readonly dedupeStringOverride?: string; 10 | readonly disambiguator?: string; 11 | } 12 | 13 | /** 14 | * Strategy used to name alarms, their widgets, and their dedupe strings. 15 | */ 16 | export interface IAlarmNamingStrategy { 17 | /** 18 | * How to generate the name of an alarm. 19 | * 20 | * @param props AlarmNamingInput 21 | */ 22 | getName(props: AlarmNamingInput): string; 23 | 24 | /** 25 | * How to generate the label for the alarm displayed on a widget. 26 | * 27 | * @param props AlarmNamingInput 28 | */ 29 | getWidgetLabel(props: AlarmNamingInput): string; 30 | 31 | /** 32 | * How to generate the deduplication string for an alarm. 33 | */ 34 | getDedupeString(props: AlarmNamingInput): string | undefined; 35 | } 36 | -------------------------------------------------------------------------------- /lib/common/alarm/action/IAlarmActionStrategy.ts: -------------------------------------------------------------------------------- 1 | import { AlarmBase } from "aws-cdk-lib/aws-cloudwatch"; 2 | 3 | import { AlarmMetadata } from "../AlarmFactory"; 4 | 5 | /** 6 | * Properties necessary to append actions to an alarm. 7 | */ 8 | export interface AlarmActionStrategyProps extends AlarmMetadata { 9 | readonly alarm: AlarmBase; 10 | } 11 | 12 | /** 13 | * An object that appends actions to alarms. 14 | */ 15 | export interface IAlarmActionStrategy { 16 | addAlarmActions(props: AlarmActionStrategyProps): void; 17 | } 18 | -------------------------------------------------------------------------------- /lib/common/alarm/action/LambdaAlarmActionStrategy.ts: -------------------------------------------------------------------------------- 1 | import { LambdaAction } from "aws-cdk-lib/aws-cloudwatch-actions"; 2 | import { IAlias, IFunction, IVersion } from "aws-cdk-lib/aws-lambda"; 3 | 4 | import { 5 | AlarmActionStrategyProps, 6 | IAlarmActionStrategy, 7 | } from "./IAlarmActionStrategy"; 8 | 9 | export function triggerLambda( 10 | lambdaFunction: IAlias | IVersion | IFunction, 11 | ): IAlarmActionStrategy { 12 | return new LambdaAlarmActionStrategy(lambdaFunction); 13 | } 14 | 15 | /** 16 | * Alarm action strategy that triggers a Lambda function. 17 | */ 18 | export class LambdaAlarmActionStrategy implements IAlarmActionStrategy { 19 | protected readonly lambdaFunction: IAlias | IVersion | IFunction; 20 | 21 | constructor(lambdaFunction: IAlias | IVersion | IFunction) { 22 | this.lambdaFunction = lambdaFunction; 23 | } 24 | 25 | addAlarmActions(props: AlarmActionStrategyProps): void { 26 | props.alarm.addAlarmAction(new LambdaAction(this.lambdaFunction)); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /lib/common/alarm/action/MultipleAlarmActionStrategy.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AlarmActionStrategyProps, 3 | IAlarmActionStrategy, 4 | } from "./IAlarmActionStrategy"; 5 | 6 | export function multipleActions(...actions: IAlarmActionStrategy[]) { 7 | return new MultipleAlarmActionStrategy(actions); 8 | } 9 | 10 | export function isMultipleAlarmActionStrategy( 11 | obj?: any, 12 | ): obj is MultipleAlarmActionStrategy { 13 | return !!(obj && obj instanceof MultipleAlarmActionStrategy); 14 | } 15 | 16 | /** 17 | * Alarm action strategy that combines multiple actions in the same order as they were given. 18 | */ 19 | export class MultipleAlarmActionStrategy implements IAlarmActionStrategy { 20 | readonly actions: IAlarmActionStrategy[]; 21 | 22 | constructor(actions: IAlarmActionStrategy[]) { 23 | this.actions = actions; 24 | } 25 | 26 | addAlarmActions(props: AlarmActionStrategyProps): void { 27 | this.actions.forEach((action) => action.addAlarmActions(props)); 28 | } 29 | 30 | /** 31 | * Returns list of alarm actions where any nested instances of MultipleAlarmActionStrategy 32 | * are flattened. 33 | * 34 | * @returns flattened list of alarm actions. 35 | */ 36 | flattenedAlarmActions(): IAlarmActionStrategy[] { 37 | return this._flattenedAlarmActions(...this.actions); 38 | } 39 | 40 | private _flattenedAlarmActions( 41 | ...actions: IAlarmActionStrategy[] 42 | ): IAlarmActionStrategy[] { 43 | return actions.flatMap((action) => { 44 | if (isMultipleAlarmActionStrategy(action)) { 45 | return this._flattenedAlarmActions(action); 46 | } 47 | 48 | return [action]; 49 | }); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /lib/common/alarm/action/NoopAlarmActionStrategy.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AlarmActionStrategyProps, 3 | IAlarmActionStrategy, 4 | } from "./IAlarmActionStrategy"; 5 | 6 | export function noopAction() { 7 | return new NoopAlarmActionStrategy(); 8 | } 9 | 10 | /** 11 | * Alarm action strategy that does not add any actions. 12 | */ 13 | export class NoopAlarmActionStrategy implements IAlarmActionStrategy { 14 | addAlarmActions(_props: AlarmActionStrategyProps): void { 15 | // No action to create. 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/common/alarm/action/OpsItemAlarmActionStrategy.ts: -------------------------------------------------------------------------------- 1 | import { 2 | OpsItemCategory, 3 | OpsItemSeverity, 4 | SsmAction, 5 | } from "aws-cdk-lib/aws-cloudwatch-actions"; 6 | import { 7 | AlarmActionStrategyProps, 8 | IAlarmActionStrategy, 9 | } from "./IAlarmActionStrategy"; 10 | 11 | /** 12 | * Creates an AWS OpsCenter OpsItem with critical severity. 13 | * 14 | * @param category optional category (no category by default) 15 | */ 16 | export function createCriticalSeverityOpsItem(category?: OpsItemCategory) { 17 | return createOpsItem(OpsItemSeverity.CRITICAL, category); 18 | } 19 | 20 | /** 21 | * Creates an AWS OpsCenter OpsItem with high severity. 22 | * 23 | * @param category optional category (no category by default) 24 | */ 25 | export function createHighSeverityOpsItem(category?: OpsItemCategory) { 26 | return createOpsItem(OpsItemSeverity.HIGH, category); 27 | } 28 | 29 | /** 30 | * Creates an AWS OpsCenter OpsItem with medium severity. 31 | * 32 | * @param category optional category (no category by default) 33 | */ 34 | export function createMediumSeverityOpsItem(category?: OpsItemCategory) { 35 | return createOpsItem(OpsItemSeverity.MEDIUM, category); 36 | } 37 | 38 | /** 39 | * Creates an AWS OpsCenter OpsItem with low severity. 40 | * 41 | * @param category optional category (no category by default) 42 | */ 43 | export function createLowSeverityOpsItem(category?: OpsItemCategory) { 44 | return createOpsItem(OpsItemSeverity.LOW, category); 45 | } 46 | 47 | /** 48 | * Creates an AWS OpsCenter OpsItem. 49 | * 50 | * @param severity desired item severity 51 | * @param category optional category (no category by default) 52 | */ 53 | export function createOpsItem( 54 | severity: OpsItemSeverity, 55 | category?: OpsItemCategory, 56 | ) { 57 | return new OpsItemAlarmActionStrategy(severity, category); 58 | } 59 | 60 | /** 61 | * Alarm action strategy that creates an AWS OpsCenter OpsItem. 62 | */ 63 | export class OpsItemAlarmActionStrategy implements IAlarmActionStrategy { 64 | /** 65 | * OPS Item Severity 66 | */ 67 | readonly severity: OpsItemSeverity; 68 | 69 | /** 70 | * OPS Item Category 71 | */ 72 | readonly category?: OpsItemCategory; 73 | 74 | constructor(severity: OpsItemSeverity, category?: OpsItemCategory) { 75 | this.severity = severity; 76 | this.category = category; 77 | } 78 | 79 | addAlarmActions(props: AlarmActionStrategyProps): void { 80 | props.alarm.addAlarmAction(new SsmAction(this.severity, this.category)); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /lib/common/alarm/action/SnsAlarmActionStrategy.ts: -------------------------------------------------------------------------------- 1 | import { SnsAction } from "aws-cdk-lib/aws-cloudwatch-actions"; 2 | import { ITopic } from "aws-cdk-lib/aws-sns"; 3 | 4 | import { 5 | AlarmActionStrategyProps, 6 | IAlarmActionStrategy, 7 | } from "./IAlarmActionStrategy"; 8 | 9 | export function notifySns( 10 | onAlarmTopic: ITopic, 11 | onOkTopic?: ITopic, 12 | onInsufficientDataTopic?: ITopic, 13 | ): IAlarmActionStrategy { 14 | return new SnsAlarmActionStrategy({ 15 | onAlarmTopic, 16 | onOkTopic, 17 | onInsufficientDataTopic, 18 | }); 19 | } 20 | 21 | export interface SnsAlarmActionStrategyProps { 22 | /** 23 | * Target topic used when the alarm is triggered. 24 | */ 25 | readonly onAlarmTopic: ITopic; 26 | 27 | /** 28 | * Optional target topic for when the alarm goes into the OK state. 29 | * 30 | * @default - no notification sent 31 | */ 32 | readonly onOkTopic?: ITopic; 33 | 34 | /** 35 | * Optional target topic for when the alarm goes into the INSUFFICIENT_DATA state. 36 | * 37 | * @default - no notification sent 38 | */ 39 | readonly onInsufficientDataTopic?: ITopic; 40 | } 41 | 42 | /** 43 | * Alarm action strategy that sends a notification to the specified SNS topic. 44 | */ 45 | export class SnsAlarmActionStrategy implements IAlarmActionStrategy { 46 | protected readonly onAlarmTopic: ITopic; 47 | protected readonly onOkTopic?: ITopic; 48 | protected readonly onInsufficientDataTopic?: ITopic; 49 | 50 | constructor(props: SnsAlarmActionStrategyProps) { 51 | this.onAlarmTopic = props.onAlarmTopic; 52 | this.onOkTopic = props.onOkTopic; 53 | this.onInsufficientDataTopic = props.onInsufficientDataTopic; 54 | } 55 | 56 | addAlarmActions(props: AlarmActionStrategyProps): void { 57 | props.alarm.addAlarmAction(new SnsAction(this.onAlarmTopic)); 58 | 59 | if (this.onOkTopic) { 60 | props.alarm.addOkAction(new SnsAction(this.onOkTopic)); 61 | } 62 | 63 | if (this.onInsufficientDataTopic) { 64 | props.alarm.addInsufficientDataAction( 65 | new SnsAction(this.onInsufficientDataTopic), 66 | ); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /lib/common/alarm/action/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./IAlarmActionStrategy"; 2 | export * from "./LambdaAlarmActionStrategy"; 3 | export * from "./MultipleAlarmActionStrategy"; 4 | export * from "./NoopAlarmActionStrategy"; 5 | export * from "./OpsItemAlarmActionStrategy"; 6 | export * from "./SnsAlarmActionStrategy"; 7 | -------------------------------------------------------------------------------- /lib/common/alarm/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./action"; 2 | export * from "./metric-adjuster"; 3 | export * from "./AlarmFactory"; 4 | export * from "./AlarmNamingStrategy"; 5 | export * from "./CustomAlarmThreshold"; 6 | export * from "./IAlarmAnnotationStrategy"; 7 | export * from "./IAlarmDedupeStringProcessor"; 8 | export * from "./IAlarmNamingStrategy"; 9 | export * from "./ScaleAlarms"; 10 | -------------------------------------------------------------------------------- /lib/common/alarm/metric-adjuster/CompositeMetricAdjuster.ts: -------------------------------------------------------------------------------- 1 | import { Construct } from "constructs"; 2 | import { IMetricAdjuster } from "./IMetricAdjuster"; 3 | import { MetricWithAlarmSupport } from "../../metric"; 4 | import { AddAlarmProps } from "../AlarmFactory"; 5 | 6 | /** 7 | * Allows to apply a collection of {@link IMetricAdjuster} to a metric. 8 | */ 9 | export class CompositeMetricAdjuster implements IMetricAdjuster { 10 | constructor(private readonly adjusters: IMetricAdjuster[]) {} 11 | 12 | static of(...adjusters: IMetricAdjuster[]) { 13 | return new CompositeMetricAdjuster(adjusters); 14 | } 15 | 16 | /** @inheritdoc */ 17 | adjustMetric( 18 | metric: MetricWithAlarmSupport, 19 | alarmScope: Construct, 20 | props: AddAlarmProps, 21 | ): MetricWithAlarmSupport { 22 | let adjustedMetric = metric; 23 | for (const adjuster of this.adjusters) { 24 | adjustedMetric = adjuster.adjustMetric(adjustedMetric, alarmScope, props); 25 | } 26 | 27 | return adjustedMetric; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/common/alarm/metric-adjuster/DefaultMetricAdjuster.ts: -------------------------------------------------------------------------------- 1 | import { Construct } from "constructs"; 2 | import { IMetricAdjuster } from "./IMetricAdjuster"; 3 | import { MetricWithAlarmSupport } from "../../metric"; 4 | import { removeBracketsWithDynamicLabels } from "../../strings"; 5 | import { AddAlarmProps } from "../AlarmFactory"; 6 | 7 | /** 8 | * Applies the default metric adjustments. 9 | * These adjustments are always applied last, regardless the value configured in {@link AddAlarmProps.metricAdjuster}. 10 | */ 11 | export class DefaultMetricAdjuster implements IMetricAdjuster { 12 | static readonly INSTANCE = new DefaultMetricAdjuster(); 13 | 14 | /** @inheritdoc */ 15 | adjustMetric( 16 | metric: MetricWithAlarmSupport, 17 | _: Construct, 18 | props: AddAlarmProps, 19 | ): MetricWithAlarmSupport { 20 | let adjustedMetric = metric; 21 | if (props.period) { 22 | // Adjust metric period for the alarm 23 | adjustedMetric = adjustedMetric.with({ period: props.period }); 24 | } 25 | 26 | if (adjustedMetric.label) { 27 | // Annotations do not support dynamic labels, so we have to remove them from metric name 28 | adjustedMetric = adjustedMetric.with({ 29 | label: removeBracketsWithDynamicLabels(adjustedMetric.label), 30 | }); 31 | } 32 | 33 | return adjustedMetric; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /lib/common/alarm/metric-adjuster/IMetricAdjuster.ts: -------------------------------------------------------------------------------- 1 | import { Construct } from "constructs"; 2 | import { MetricWithAlarmSupport } from "../../metric"; 3 | import { AddAlarmProps } from "../AlarmFactory"; 4 | 5 | /** 6 | * Adjusts a metric before creating adding an alarm to it. 7 | */ 8 | export interface IMetricAdjuster { 9 | /** 10 | * Adjusts a metric. 11 | * @param metric The metric to adjust. 12 | * @param alarmScope The alarm scope. 13 | * @param props The props specified for adding the alarm. 14 | * @returns The adjusted metric. 15 | */ 16 | adjustMetric( 17 | metric: MetricWithAlarmSupport, 18 | alarmScope: Construct, 19 | props: AddAlarmProps, 20 | ): MetricWithAlarmSupport; 21 | } 22 | -------------------------------------------------------------------------------- /lib/common/alarm/metric-adjuster/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./CompositeMetricAdjuster"; 2 | export * from "./DefaultMetricAdjuster"; 3 | export * from "./IMetricAdjuster"; 4 | export * from "./Route53HealthCheckMetricAdjuster"; 5 | -------------------------------------------------------------------------------- /lib/common/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./alarm"; 2 | export * from "./metric"; 3 | export * from "./monitoring"; 4 | export * from "./url"; 5 | export * from "./widget"; 6 | export * from "./strings"; 7 | -------------------------------------------------------------------------------- /lib/common/metric/AnomalyDetectionMathExpression.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Alarm, 3 | CfnAlarm, 4 | CreateAlarmOptions, 5 | MathExpression, 6 | MathExpressionOptions, 7 | MathExpressionProps, 8 | } from "aws-cdk-lib/aws-cloudwatch"; 9 | import { Construct } from "constructs"; 10 | 11 | /** 12 | * Captures specific MathExpression for anomaly detection, for which alarm generation is different. 13 | * Added to overcome certain CDK limitations at the time of writing. 14 | * @see https://github.com/aws/aws-cdk/issues/10540 15 | */ 16 | export class AnomalyDetectionMathExpression extends MathExpression { 17 | constructor(props: MathExpressionProps) { 18 | super(props); 19 | } 20 | 21 | with(props: MathExpressionOptions): MathExpression { 22 | return new AnomalyDetectionMathExpression({ 23 | expression: this.expression, 24 | usingMetrics: this.usingMetrics, 25 | label: props.label ?? this.label, 26 | color: props.color ?? this.color, 27 | period: props.period ?? this.period, 28 | }); 29 | } 30 | 31 | createAlarm(scope: Construct, id: string, props: CreateAlarmOptions): Alarm { 32 | const alarm = super.createAlarm(scope, id, props); 33 | 34 | // `usingMetrics` of an anomaly detection alarm can only ever have one entry. 35 | // Should the entry be a math expression, the math expression can have its own `usingMetrics`. 36 | const finalExpressionId = Object.keys(this.usingMetrics)[0]; 37 | 38 | // https://github.com/aws/aws-cdk/issues/10540#issuecomment-725222564 39 | const cfnAlarm = alarm.node.defaultChild as CfnAlarm; 40 | cfnAlarm.addPropertyDeletionOverride("Threshold"); 41 | (cfnAlarm.metrics as CfnAlarm.MetricDataQueryProperty[]).forEach( 42 | (metric, index) => { 43 | // To create an anomaly detection alarm, returned data should be set to true on two MetricDataQueryProperty(s): 44 | // 1. The metric or math expression that is being evaluated for anomaly detection (eg. expr_1) 45 | // 2. The actual expression of anomaly detection (eg. ANOMALY_DETECTION_BAND(expr_1, 1)) 46 | let returnData = false; 47 | 48 | if (metric.expression?.includes("ANOMALY_DETECTION_BAND")) { 49 | // thresholdMetricId is the ID of the ANOMALY_DETECTION_BAND function used as the threshold for the alarm. 50 | cfnAlarm.thresholdMetricId = metric.id; 51 | returnData = true; 52 | } else if (metric.id === finalExpressionId) { 53 | returnData = true; 54 | } 55 | 56 | cfnAlarm.addPropertyOverride(`Metrics.${index}.ReturnData`, returnData); 57 | }, 58 | ); 59 | 60 | return alarm; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /lib/common/metric/BaseMetricFactory.ts: -------------------------------------------------------------------------------- 1 | import { MetricFactory } from "./MetricFactory"; 2 | 3 | export interface BaseMetricFactoryProps { 4 | /** 5 | * Region where the metrics exist. 6 | * 7 | * @default The region configured by the construct holding the Monitoring construct 8 | * @see https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/Cross-Account-Cross-Region.html 9 | */ 10 | readonly region?: string; 11 | 12 | /** 13 | * Account where the metrics exist. 14 | * 15 | * @default The account configured by the construct holding the Monitoring construct 16 | * @see https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/Cross-Account-Cross-Region.html 17 | */ 18 | readonly account?: string; 19 | } 20 | 21 | export abstract class BaseMetricFactory< 22 | PropsType extends BaseMetricFactoryProps, 23 | > { 24 | protected readonly metricFactory: MetricFactory; 25 | protected readonly account?: string; 26 | protected readonly region?: string; 27 | 28 | constructor(metricFactory: MetricFactory, props: PropsType) { 29 | this.metricFactory = metricFactory; 30 | this.account = props.account; 31 | this.region = props.region; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /lib/common/metric/MetricWithAlarmSupport.ts: -------------------------------------------------------------------------------- 1 | import { MathExpression, Metric } from "aws-cdk-lib/aws-cloudwatch"; 2 | 3 | /** 4 | * Any metric we can create an alarm on. 5 | * 6 | * Cannot be an IMetric, as it does not have support for alarms. 7 | */ 8 | export type MetricWithAlarmSupport = Metric | MathExpression; 9 | -------------------------------------------------------------------------------- /lib/common/metric/RateComputationMethod.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Enumeration of different rate computation methods. 3 | */ 4 | export enum RateComputationMethod { 5 | /** 6 | * Number of occurrences relative to requests. 7 | * Less sensitive than per-second when TPS > 1. 8 | */ 9 | AVERAGE, 10 | /** 11 | * Number of occurrences per second. 12 | * More sensitive than average when TPS > 1. 13 | */ 14 | PER_SECOND, 15 | /** 16 | * Number of occurrences per minute. 17 | */ 18 | PER_MINUTE, 19 | /** 20 | * Number of occurrences per hour. 21 | */ 22 | PER_HOUR, 23 | /** 24 | * Number of occurrences per day. 25 | */ 26 | PER_DAY, 27 | } 28 | -------------------------------------------------------------------------------- /lib/common/metric/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./AnomalyDetectionMathExpression"; 2 | export * from "./BaseMetricFactory"; 3 | export * from "./MetricFactory"; 4 | export * from "./MetricStatistic"; 5 | export * from "./MetricWithAlarmSupport"; 6 | export * from "./RateComputationMethod"; 7 | -------------------------------------------------------------------------------- /lib/common/monitoring/MonitoringScope.ts: -------------------------------------------------------------------------------- 1 | import { Construct } from "constructs"; 2 | 3 | import { IWidgetFactory } from "../../dashboard"; 4 | import { AlarmFactory } from "../alarm"; 5 | import { MetricFactory } from "../metric"; 6 | import { AwsConsoleUrlFactory } from "../url"; 7 | 8 | /** 9 | * A scope where all monitored constructs are managed from (i.e., alarms, dashboards, etc.). 10 | * 11 | * Standard usages will use {@link MonitoringFacade}. 12 | */ 13 | export abstract class MonitoringScope extends Construct { 14 | /** 15 | * Creates a new widget factory. 16 | */ 17 | abstract createWidgetFactory(): IWidgetFactory; 18 | 19 | /** 20 | * Creates a new metric factory. 21 | */ 22 | abstract createMetricFactory(): MetricFactory; 23 | 24 | /** 25 | * Creates a new alarm factory. 26 | * Alarms created will be named with the given prefix, unless a local name override is present. 27 | * 28 | * @param alarmNamePrefix alarm name prefix 29 | */ 30 | abstract createAlarmFactory(alarmNamePrefix: string): AlarmFactory; 31 | 32 | /** 33 | * Creates a new factory that creates AWS Console URLs. 34 | */ 35 | abstract createAwsConsoleUrlFactory(): AwsConsoleUrlFactory; 36 | } 37 | -------------------------------------------------------------------------------- /lib/common/monitoring/alarms/AnomalyDetectingAlarmFactory.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ComparisonOperator, 3 | TreatMissingData, 4 | } from "aws-cdk-lib/aws-cloudwatch"; 5 | 6 | import { AlarmFactory, CustomAlarmThreshold } from "../../alarm"; 7 | import { MetricWithAlarmSupport } from "../../metric"; 8 | 9 | export interface AnomalyDetectionThreshold extends CustomAlarmThreshold { 10 | readonly standardDeviationForAlarm: number; 11 | readonly alarmWhenAboveTheBand: boolean; 12 | readonly alarmWhenBelowTheBand: boolean; 13 | readonly additionalDescription?: string; 14 | } 15 | 16 | export class AnomalyDetectingAlarmFactory { 17 | protected readonly alarmFactory: AlarmFactory; 18 | 19 | constructor(alarmFactory: AlarmFactory) { 20 | this.alarmFactory = alarmFactory; 21 | } 22 | 23 | addAlarmWhenOutOfBand( 24 | metric: MetricWithAlarmSupport, 25 | alarmNameSuffix: string, 26 | disambiguator: string, 27 | props: AnomalyDetectionThreshold, 28 | ) { 29 | return this.alarmFactory.addAlarm(metric, { 30 | ...props, 31 | disambiguator, 32 | treatMissingData: 33 | props.treatMissingDataOverride ?? TreatMissingData.MISSING, 34 | // Dummy threshold value. This gets removed later. 35 | threshold: 0, 36 | comparisonOperator: this.getComparisonOperator(props), 37 | alarmDedupeStringSuffix: props.dedupeStringOverride, 38 | alarmNameSuffix, 39 | alarmDescription: 40 | props.additionalDescription ?? this.getDefaultDescription(props), 41 | }); 42 | } 43 | 44 | private getDefaultDescription(props: AnomalyDetectionThreshold) { 45 | if (props.alarmWhenAboveTheBand && props.alarmWhenBelowTheBand) { 46 | return "Anomaly detection: value is outside of the expected band."; 47 | } else if (props.alarmWhenAboveTheBand) { 48 | return "Anomaly detection: value is above the expected band."; 49 | } else if (props.alarmWhenBelowTheBand) { 50 | return "Anomaly detection: value is below the expected band."; 51 | } else { 52 | throw new Error( 53 | "You need to alarm when the value is above or below the band, or both.", 54 | ); 55 | } 56 | } 57 | 58 | private getComparisonOperator(props: AnomalyDetectionThreshold) { 59 | if (props.alarmWhenAboveTheBand && props.alarmWhenBelowTheBand) { 60 | return ComparisonOperator.LESS_THAN_LOWER_OR_GREATER_THAN_UPPER_THRESHOLD; 61 | } else if (props.alarmWhenAboveTheBand) { 62 | return ComparisonOperator.GREATER_THAN_UPPER_THRESHOLD; 63 | } else if (props.alarmWhenBelowTheBand) { 64 | return ComparisonOperator.LESS_THAN_LOWER_THRESHOLD; 65 | } else { 66 | throw new Error( 67 | "You need to alarm when the value is above or below the band, or both.", 68 | ); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /lib/common/monitoring/alarms/AuroraAlarmFactory.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ComparisonOperator, 3 | TreatMissingData, 4 | } from "aws-cdk-lib/aws-cloudwatch"; 5 | 6 | import { AlarmFactory, CustomAlarmThreshold } from "../../alarm"; 7 | import { MetricWithAlarmSupport } from "../../metric"; 8 | 9 | export interface HighServerlessDatabaseCapacityThreshold 10 | extends CustomAlarmThreshold { 11 | readonly maxServerlessDatabaseCapacity: number; 12 | } 13 | 14 | export class AuroraAlarmFactory { 15 | protected readonly alarmFactory: AlarmFactory; 16 | 17 | constructor(alarmFactory: AlarmFactory) { 18 | this.alarmFactory = alarmFactory; 19 | } 20 | 21 | addMaxServerlessDatabaseCapacity( 22 | metric: MetricWithAlarmSupport, 23 | props: HighServerlessDatabaseCapacityThreshold, 24 | disambiguator?: string, 25 | ) { 26 | return this.alarmFactory.addAlarm(metric, { 27 | treatMissingData: 28 | props.treatMissingDataOverride ?? TreatMissingData.MISSING, 29 | comparisonOperator: 30 | props.comparisonOperatorOverride ?? 31 | ComparisonOperator.GREATER_THAN_THRESHOLD, 32 | ...props, 33 | disambiguator, 34 | threshold: props.maxServerlessDatabaseCapacity, 35 | alarmNameSuffix: "Serverless-Database-Capacity-High", 36 | alarmDescription: "Serverless Database Capacity usage is too high.", 37 | }); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /lib/common/monitoring/alarms/ConnectionAlarmFactory.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ComparisonOperator, 3 | TreatMissingData, 4 | } from "aws-cdk-lib/aws-cloudwatch"; 5 | 6 | import { AlarmFactory, CustomAlarmThreshold } from "../../alarm"; 7 | import { MetricWithAlarmSupport } from "../../metric"; 8 | 9 | export interface LowConnectionCountThreshold extends CustomAlarmThreshold { 10 | readonly minConnectionCount: number; 11 | } 12 | 13 | export interface HighConnectionCountThreshold extends CustomAlarmThreshold { 14 | readonly maxConnectionCount: number; 15 | } 16 | 17 | export class ConnectionAlarmFactory { 18 | protected readonly alarmFactory: AlarmFactory; 19 | 20 | constructor(alarmFactory: AlarmFactory) { 21 | this.alarmFactory = alarmFactory; 22 | } 23 | 24 | addMinConnectionCountAlarm( 25 | metric: MetricWithAlarmSupport, 26 | props: LowConnectionCountThreshold, 27 | disambiguator?: string, 28 | ) { 29 | return this.alarmFactory.addAlarm(metric, { 30 | treatMissingData: 31 | props.treatMissingDataOverride ?? TreatMissingData.MISSING, 32 | comparisonOperator: 33 | props.comparisonOperatorOverride ?? 34 | ComparisonOperator.LESS_THAN_THRESHOLD, 35 | ...props, 36 | disambiguator, 37 | threshold: props.minConnectionCount, 38 | alarmNameSuffix: "Connection-Count-Low", 39 | alarmDescription: `Number of connections is too low.`, 40 | }); 41 | } 42 | 43 | addMaxConnectionCountAlarm( 44 | metric: MetricWithAlarmSupport, 45 | props: HighConnectionCountThreshold, 46 | disambiguator?: string, 47 | ) { 48 | return this.alarmFactory.addAlarm(metric, { 49 | treatMissingData: 50 | props.treatMissingDataOverride ?? TreatMissingData.MISSING, 51 | comparisonOperator: 52 | props.comparisonOperatorOverride ?? 53 | ComparisonOperator.GREATER_THAN_THRESHOLD, 54 | ...props, 55 | disambiguator, 56 | threshold: props.maxConnectionCount, 57 | alarmNameSuffix: "Connection-Count-High", 58 | alarmDescription: `Number of connections is too high.`, 59 | }); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /lib/common/monitoring/alarms/CustomAlarmFactory.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ComparisonOperator, 3 | TreatMissingData, 4 | } from "aws-cdk-lib/aws-cloudwatch"; 5 | 6 | import { AlarmFactory, CustomAlarmThreshold } from "../../alarm"; 7 | import { MetricWithAlarmSupport } from "../../metric"; 8 | 9 | export interface CustomThreshold extends CustomAlarmThreshold { 10 | readonly threshold: number; 11 | readonly comparisonOperator: ComparisonOperator; 12 | readonly dedupeString?: string; 13 | readonly additionalDescription?: string; 14 | } 15 | 16 | export class CustomAlarmFactory { 17 | protected readonly alarmFactory: AlarmFactory; 18 | 19 | constructor(alarmFactory: AlarmFactory) { 20 | this.alarmFactory = alarmFactory; 21 | } 22 | 23 | addCustomAlarm( 24 | metric: MetricWithAlarmSupport, 25 | alarmNameSuffix: string, 26 | disambiguator: string, 27 | props: CustomThreshold, 28 | ) { 29 | return this.alarmFactory.addAlarm(metric, { 30 | ...props, 31 | disambiguator, 32 | treatMissingData: 33 | props.treatMissingDataOverride ?? TreatMissingData.MISSING, 34 | threshold: props.threshold, 35 | comparisonOperator: 36 | props.comparisonOperatorOverride ?? props.comparisonOperator, 37 | alarmDedupeStringSuffix: props.dedupeString, 38 | alarmNameSuffix, 39 | alarmDescription: 40 | props.additionalDescription ?? 41 | `Threshold of ${props.threshold} has been breached.`, 42 | }); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /lib/common/monitoring/alarms/DynamoAlarmFactory.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ComparisonOperator, 3 | TreatMissingData, 4 | } from "aws-cdk-lib/aws-cloudwatch"; 5 | 6 | import { AlarmFactory, CustomAlarmThreshold } from "../../alarm"; 7 | import { MetricWithAlarmSupport } from "../../metric"; 8 | 9 | export enum CapacityType { 10 | READ = "Read", 11 | WRITE = "Write", 12 | } 13 | 14 | export interface ConsumedCapacityThreshold extends CustomAlarmThreshold { 15 | readonly maxConsumedCapacityUnits: number; 16 | } 17 | 18 | export interface ThrottledEventsThreshold extends CustomAlarmThreshold { 19 | readonly maxThrottledEventsThreshold: number; 20 | } 21 | 22 | export class DynamoAlarmFactory { 23 | protected readonly alarmFactory: AlarmFactory; 24 | 25 | constructor(alarmFactory: AlarmFactory) { 26 | this.alarmFactory = alarmFactory; 27 | } 28 | 29 | addConsumedCapacityAlarm( 30 | metric: MetricWithAlarmSupport, 31 | capacityType: CapacityType, 32 | props: ConsumedCapacityThreshold, 33 | disambiguator?: string, 34 | ) { 35 | return this.alarmFactory.addAlarm(metric, { 36 | treatMissingData: 37 | props.treatMissingDataOverride ?? TreatMissingData.NOT_BREACHING, 38 | comparisonOperator: 39 | props.comparisonOperatorOverride ?? 40 | ComparisonOperator.GREATER_THAN_THRESHOLD, 41 | ...props, 42 | disambiguator, 43 | threshold: props.maxConsumedCapacityUnits, 44 | alarmNameSuffix: `${capacityType}-Consumed-Capacity`, 45 | // we will dedupe any kind of error to the same ticket 46 | alarmDedupeStringSuffix: "ConsumedCapacity", 47 | alarmDescription: `${capacityType} consumed capacity is too high.`, 48 | }); 49 | } 50 | 51 | addThrottledEventsAlarm( 52 | metric: MetricWithAlarmSupport, 53 | capacityType: CapacityType, 54 | props: ThrottledEventsThreshold, 55 | disambiguator?: string, 56 | ) { 57 | return this.alarmFactory.addAlarm(metric, { 58 | treatMissingData: 59 | props.treatMissingDataOverride ?? TreatMissingData.NOT_BREACHING, 60 | comparisonOperator: 61 | props.comparisonOperatorOverride ?? 62 | ComparisonOperator.GREATER_THAN_THRESHOLD, 63 | ...props, 64 | disambiguator, 65 | threshold: props.maxThrottledEventsThreshold, 66 | alarmNameSuffix: `${capacityType}-Throttled-Events`, 67 | // we will dedupe any kind of error to the same ticket 68 | alarmDedupeStringSuffix: "ThrottledEvents", 69 | alarmDescription: `${capacityType} throttled events above threshold.`, 70 | }); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /lib/common/monitoring/alarms/KinesisDataAnalyticsAlarmFactory.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ComparisonOperator, 3 | TreatMissingData, 4 | } from "aws-cdk-lib/aws-cloudwatch"; 5 | 6 | import { AlarmFactory, CustomAlarmThreshold } from "../../alarm"; 7 | import { MetricWithAlarmSupport } from "../../metric"; 8 | 9 | export interface MaxDowntimeThreshold extends CustomAlarmThreshold { 10 | readonly maxDowntimeInMillis: number; 11 | } 12 | 13 | export interface FullRestartCountThreshold extends CustomAlarmThreshold { 14 | readonly maxFullRestartCount: number; 15 | } 16 | 17 | export class KinesisDataAnalyticsAlarmFactory { 18 | protected readonly alarmFactory: AlarmFactory; 19 | 20 | constructor(alarmFactory: AlarmFactory) { 21 | this.alarmFactory = alarmFactory; 22 | } 23 | 24 | addDowntimeAlarm( 25 | metric: MetricWithAlarmSupport, 26 | props: MaxDowntimeThreshold, 27 | disambiguator?: string, 28 | ) { 29 | return this.alarmFactory.addAlarm(metric, { 30 | treatMissingData: 31 | props.treatMissingDataOverride ?? TreatMissingData.BREACHING, 32 | comparisonOperator: 33 | props.comparisonOperatorOverride ?? 34 | ComparisonOperator.GREATER_THAN_THRESHOLD, 35 | ...props, 36 | disambiguator, 37 | threshold: props.maxDowntimeInMillis, 38 | alarmNameSuffix: "Downtime", 39 | alarmDescription: "Application has too much downtime", 40 | // we will dedupe any kind of message count issue to the same ticket 41 | alarmDedupeStringSuffix: "KDADowntimeAlarm", 42 | }); 43 | } 44 | 45 | addFullRestartAlarm( 46 | metric: MetricWithAlarmSupport, 47 | props: FullRestartCountThreshold, 48 | disambiguator?: string, 49 | ) { 50 | return this.alarmFactory.addAlarm(metric, { 51 | treatMissingData: 52 | props.treatMissingDataOverride ?? TreatMissingData.BREACHING, 53 | comparisonOperator: 54 | props.comparisonOperatorOverride ?? 55 | ComparisonOperator.GREATER_THAN_THRESHOLD, 56 | ...props, 57 | disambiguator, 58 | threshold: props.maxFullRestartCount, 59 | alarmNameSuffix: "FullRestart", 60 | alarmDescription: "Last submitted job is restarting more than usual", 61 | alarmDedupeStringSuffix: "KDAFullRestartAlarm", 62 | }); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /lib/common/monitoring/alarms/LogLevelAlarmFactory.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ComparisonOperator, 3 | TreatMissingData, 4 | } from "aws-cdk-lib/aws-cloudwatch"; 5 | 6 | import { AlarmFactory, CustomAlarmThreshold } from "../../alarm"; 7 | import { MetricWithAlarmSupport } from "../../metric"; 8 | 9 | /** 10 | * Level of a given log 11 | */ 12 | export enum LogLevel { 13 | ERROR = "ERROR", 14 | CRITICAL = "CRITICAL", 15 | FATAL = "FATAL", 16 | } 17 | 18 | export interface LogLevelCountThreshold extends CustomAlarmThreshold { 19 | /** 20 | * Threshold for the number of logs to alarm on 21 | */ 22 | readonly maxLogCount: number; 23 | } 24 | 25 | export class LogLevelAlarmFactory { 26 | protected readonly alarmFactory: AlarmFactory; 27 | 28 | constructor(alarmFactory: AlarmFactory) { 29 | this.alarmFactory = alarmFactory; 30 | } 31 | 32 | addLogCountAlarm( 33 | metric: MetricWithAlarmSupport, 34 | logLevel: LogLevel, 35 | props: LogLevelCountThreshold, 36 | disambiguator?: string, 37 | ) { 38 | return this.alarmFactory.addAlarm(metric, { 39 | treatMissingData: 40 | props.treatMissingDataOverride ?? TreatMissingData.NOT_BREACHING, 41 | comparisonOperator: 42 | props.comparisonOperatorOverride ?? 43 | ComparisonOperator.GREATER_THAN_THRESHOLD, 44 | ...props, 45 | disambiguator, 46 | threshold: props.maxLogCount, 47 | alarmNameSuffix: `${LogLevel[logLevel]}-Logs-Count`, 48 | // we will dedupe any kind of error to the same ticket 49 | alarmDedupeStringSuffix: `${LogLevel[logLevel].toLowerCase()}`, 50 | alarmDescription: `${LogLevel[logLevel]} logs count is too high.`, 51 | }); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /lib/common/monitoring/alarms/ThroughputAlarmFactory.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ComparisonOperator, 3 | TreatMissingData, 4 | } from "aws-cdk-lib/aws-cloudwatch"; 5 | 6 | import { AlarmFactory, CustomAlarmThreshold } from "../../alarm"; 7 | import { MetricWithAlarmSupport } from "../../metric"; 8 | 9 | export interface MinProcessedBytesThreshold extends CustomAlarmThreshold { 10 | /** 11 | * Threshold for the least number of bytes processed 12 | */ 13 | readonly minProcessedBytes: number; 14 | } 15 | 16 | export class ThroughputAlarmFactory { 17 | protected readonly alarmFactory: AlarmFactory; 18 | 19 | constructor(alarmFactory: AlarmFactory) { 20 | this.alarmFactory = alarmFactory; 21 | } 22 | 23 | addMinProcessedBytesAlarm( 24 | metric: MetricWithAlarmSupport, 25 | props: MinProcessedBytesThreshold, 26 | disambiguator?: string, 27 | ) { 28 | return this.alarmFactory.addAlarm(metric, { 29 | treatMissingData: 30 | props.treatMissingDataOverride ?? TreatMissingData.NOT_BREACHING, 31 | comparisonOperator: 32 | props.comparisonOperatorOverride ?? 33 | ComparisonOperator.LESS_THAN_THRESHOLD, 34 | ...props, 35 | disambiguator, 36 | threshold: props.minProcessedBytes, 37 | alarmNameSuffix: "Processed-Bytes-Min", 38 | alarmDescription: `Minimum number of processed bytes is too low.`, 39 | }); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /lib/common/monitoring/alarms/TpsAlarmFactory.ts: -------------------------------------------------------------------------------- 1 | import { 2 | ComparisonOperator, 3 | TreatMissingData, 4 | } from "aws-cdk-lib/aws-cloudwatch"; 5 | 6 | import { AlarmFactory, CustomAlarmThreshold } from "../../alarm"; 7 | import { MetricWithAlarmSupport } from "../../metric"; 8 | 9 | export interface LowTpsThreshold extends CustomAlarmThreshold { 10 | readonly minTps: number; 11 | } 12 | 13 | export interface HighTpsThreshold extends CustomAlarmThreshold { 14 | readonly maxTps: number; 15 | } 16 | 17 | export class TpsAlarmFactory { 18 | protected readonly alarmFactory: AlarmFactory; 19 | 20 | constructor(alarmFactory: AlarmFactory) { 21 | this.alarmFactory = alarmFactory; 22 | } 23 | 24 | addMinTpsAlarm( 25 | metric: MetricWithAlarmSupport, 26 | props: LowTpsThreshold, 27 | disambiguator?: string, 28 | ) { 29 | return this.alarmFactory.addAlarm(metric, { 30 | treatMissingData: 31 | props.treatMissingDataOverride ?? TreatMissingData.MISSING, 32 | comparisonOperator: 33 | props.comparisonOperatorOverride ?? 34 | ComparisonOperator.LESS_THAN_THRESHOLD, 35 | ...props, 36 | disambiguator, 37 | threshold: props.minTps, 38 | alarmNameSuffix: `MinTPS`, 39 | alarmDedupeStringSuffix: "Tps-Min", 40 | alarmDescription: `TPS is too low.`, 41 | }); 42 | } 43 | 44 | addMaxTpsAlarm( 45 | metric: MetricWithAlarmSupport, 46 | props: HighTpsThreshold, 47 | disambiguator?: string, 48 | ) { 49 | return this.alarmFactory.addAlarm(metric, { 50 | treatMissingData: 51 | props.treatMissingDataOverride ?? TreatMissingData.MISSING, 52 | comparisonOperator: 53 | props.comparisonOperatorOverride ?? 54 | ComparisonOperator.GREATER_THAN_THRESHOLD, 55 | ...props, 56 | disambiguator, 57 | threshold: props.maxTps, 58 | alarmNameSuffix: `MaxTPS`, 59 | alarmDedupeStringSuffix: "Tps-Max", 60 | alarmDescription: `TPS is too high.`, 61 | }); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /lib/common/monitoring/alarms/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./AgeAlarmFactory"; 2 | export * from "./AnomalyDetectingAlarmFactory"; 3 | export * from "./AuroraAlarmFactory"; 4 | export * from "./CustomAlarmFactory"; 5 | export * from "./ConnectionAlarmFactory"; 6 | export * from "./DynamoAlarmFactory"; 7 | export * from "./ElastiCacheAlarmFactory"; 8 | export * from "./ErrorAlarmFactory"; 9 | export * from "./KinesisAlarmFactory"; 10 | export * from "./KinesisDataAnalyticsAlarmFactory"; 11 | export * from "./LatencyAlarmFactory"; 12 | export * from "./LogLevelAlarmFactory"; 13 | export * from "./OpenSearchClusterAlarmFactory"; 14 | export * from "./QueueAlarmFactory"; 15 | export * from "./SecretsManagerAlarmFactory"; 16 | export * from "./TaskHealthAlarmFactory"; 17 | export * from "./ThroughputAlarmFactory"; 18 | export * from "./TopicAlarmFactory"; 19 | export * from "./TpsAlarmFactory"; 20 | export * from "./UsageAlarmFactory"; 21 | -------------------------------------------------------------------------------- /lib/common/monitoring/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./alarms"; 2 | export * from "./Monitoring"; 3 | export * from "./MonitoringScope"; 4 | -------------------------------------------------------------------------------- /lib/common/url/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./AwsConsoleUrlFactory"; 2 | -------------------------------------------------------------------------------- /lib/common/widget/color.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * color to indicate healthy state 3 | */ 4 | export const HealthyMetricColor = "#2ca02c"; 5 | /** 6 | * color to indicate unhealthy state 7 | */ 8 | export const UnhealthyMetricColor = "#d62728"; 9 | /** 10 | * color to indicate a warning 11 | */ 12 | export const WarningColor = "#ff9900"; 13 | /** 14 | * color to indicate an error 15 | */ 16 | export const ErrorColor = "#d13212"; 17 | /** 18 | * color to indicate neutral information 19 | */ 20 | export const NeutralColor = "#999999"; 21 | /** 22 | * color to indicate throttled state 23 | */ 24 | export const ThrottledColor = "#5928ed"; 25 | /** 26 | * color to indicate a started state (e.g. Step Function executions) 27 | */ 28 | export const StartedColor = "#0073e6"; 29 | /** 30 | * color to indicate a timed out state 31 | */ 32 | export const TimedOutColor = "#333333"; 33 | -------------------------------------------------------------------------------- /lib/common/widget/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./axis"; 2 | export * from "./color"; 3 | export * from "./size"; 4 | export * from "./types"; 5 | -------------------------------------------------------------------------------- /lib/common/widget/size.ts: -------------------------------------------------------------------------------- 1 | export const FullWidth = 24; 2 | export const HalfWidth = FullWidth / 2; 3 | export const ThirdWidth = FullWidth / 3; 4 | export const QuarterWidth = FullWidth / 4; 5 | export const HalfQuarterWidth = QuarterWidth / 2; 6 | export const SixthWidth = FullWidth / 6; 7 | export const TwoThirdsWidth = 2 * ThirdWidth; 8 | export const ThreeQuartersWidth = 3 * QuarterWidth; 9 | 10 | // Widget Heights 11 | 12 | export const DefaultGraphWidgetHeight = 5; 13 | export const DefaultTwoLinerGraphWidgetHeight = 6; 14 | export const DefaultTwoLinerGraphWidgetHalfHeight = 3; 15 | export const DefaultSummaryWidgetHeight = 6; 16 | export const DefaultAlarmWidgetWidth = 6; 17 | export const DefaultAlarmWidgetHeight = 4; 18 | export const DefaultLogWidgetHeight = 7; 19 | 20 | /** 21 | * Suggests the best widget width, given the total number of widgets. 22 | * The main point is to make widgets as wide as possible, while saving vertical space and minimizing number of gaps. 23 | * 24 | * @param numTotalWidgets total number of widgets to be placed 25 | */ 26 | export function recommendedWidgetWidth(numTotalWidgets: number) { 27 | function numRowsTaken(numItems: number, itemSize: number) { 28 | return Math.ceil((numItems * itemSize) / FullWidth); 29 | } 30 | 31 | const numItemsFixed = Math.max(1, numTotalWidgets); 32 | const widths = [QuarterWidth, ThirdWidth, HalfWidth, FullWidth]; 33 | let i = 0; 34 | while ( 35 | i < widths.length - 1 && 36 | numRowsTaken(numItemsFixed, widths[i + 1]) === 37 | numRowsTaken(numItemsFixed, widths[i]) 38 | ) { 39 | i++; 40 | } 41 | return widths[i]; 42 | } 43 | -------------------------------------------------------------------------------- /lib/common/widget/types.ts: -------------------------------------------------------------------------------- 1 | import { 2 | GraphWidget, 3 | GraphWidgetProps, 4 | GraphWidgetView, 5 | IWidget, 6 | SingleValueWidget, 7 | TableWidget, 8 | } from "aws-cdk-lib/aws-cloudwatch"; 9 | 10 | export enum GraphWidgetType { 11 | BAR = "Bar", 12 | LINE = "Line", 13 | PIE = "Pie", 14 | SINGLE_VALUE = "SingleValue", 15 | STACKED_AREA = "StackedArea", 16 | TABLE = "Table", 17 | } 18 | 19 | /** 20 | * Creates a graph widget of the desired type. 21 | * 22 | * @param type graph type (e.g. Pie or Bar) 23 | * @param props graph widget properties 24 | */ 25 | export function createGraphWidget( 26 | type: GraphWidgetType, 27 | props: GraphWidgetProps, 28 | ): IWidget { 29 | switch (type) { 30 | case GraphWidgetType.BAR: 31 | return new GraphWidget({ 32 | ...props, 33 | view: GraphWidgetView.BAR, 34 | }); 35 | 36 | case GraphWidgetType.LINE: 37 | return new GraphWidget(props); 38 | 39 | case GraphWidgetType.PIE: 40 | return new GraphWidget({ 41 | ...props, 42 | view: GraphWidgetView.PIE, 43 | }); 44 | 45 | case GraphWidgetType.SINGLE_VALUE: 46 | return new SingleValueWidget({ 47 | ...props, 48 | metrics: [...(props.left ?? []), ...(props.right ?? [])], 49 | }); 50 | 51 | case GraphWidgetType.STACKED_AREA: 52 | return new GraphWidget({ 53 | ...props, 54 | stacked: true, 55 | }); 56 | 57 | case GraphWidgetType.TABLE: 58 | return new TableWidget({ 59 | ...props, 60 | metrics: [...(props.left ?? []), ...(props.right ?? [])], 61 | }); 62 | 63 | default: 64 | throw new Error(`Unsupported graph type: ${type}`); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /lib/dashboard/BitmapDashboard.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Column, 3 | Dashboard, 4 | DashboardProps, 5 | GraphWidget, 6 | IWidget, 7 | Row, 8 | } from "aws-cdk-lib/aws-cloudwatch"; 9 | import { Construct } from "constructs"; 10 | 11 | import { BitmapWidgetRenderingSupport } from "./widget"; 12 | 13 | /** 14 | * Specific subtype of dashboard that renders supported widgets as bitmaps, while preserving the overall layout. 15 | */ 16 | export class BitmapDashboard extends Dashboard { 17 | protected readonly bitmapRenderingSupport: BitmapWidgetRenderingSupport; 18 | 19 | constructor(scope: Construct, id: string, props: DashboardProps) { 20 | super(scope, id, props); 21 | this.bitmapRenderingSupport = new BitmapWidgetRenderingSupport( 22 | this, 23 | "BitmapRenderingSupport", 24 | ); 25 | } 26 | 27 | addWidgets(...widgets: IWidget[]) { 28 | super.addWidgets(...this.asBitmaps(...widgets)); 29 | } 30 | 31 | protected asBitmap(widget: IWidget): IWidget { 32 | if (widget instanceof GraphWidget) { 33 | return this.bitmapRenderingSupport.asBitmap(widget); 34 | } else if (widget instanceof Row) { 35 | // needs this to access private property 36 | const rowWidgets = (widget as any).widgets; 37 | const rowWidgetsTyped = rowWidgets as IWidget[]; 38 | return new Row(...this.asBitmaps(...rowWidgetsTyped)); 39 | } else if (widget instanceof Column) { 40 | // needs this to access private property 41 | const colWidgets = (widget as any).widgets; 42 | const colWidgetsTyped = colWidgets as IWidget[]; 43 | return new Column(...this.asBitmaps(...colWidgetsTyped)); 44 | } else { 45 | return widget; 46 | } 47 | } 48 | 49 | protected asBitmaps(...widgets: IWidget[]): IWidget[] { 50 | return widgets.map((widget) => this.asBitmap(widget)); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /lib/dashboard/DashboardRenderingPreference.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Preferred way of rendering dashboard widgets. 3 | */ 4 | export enum DashboardRenderingPreference { 5 | /** 6 | * Create standard set of dashboards with interactive widgets only 7 | */ 8 | INTERACTIVE_ONLY, 9 | 10 | /** 11 | * Create standard set of dashboards with bitmap widgets only 12 | */ 13 | BITMAP_ONLY, 14 | 15 | /** 16 | * Create a two sets of dashboards: standard set (interactive) and a copy (bitmap) 17 | */ 18 | INTERACTIVE_AND_BITMAP, 19 | } 20 | -------------------------------------------------------------------------------- /lib/dashboard/DashboardSegment.ts: -------------------------------------------------------------------------------- 1 | import { IWidget } from "aws-cdk-lib/aws-cloudwatch"; 2 | 3 | export interface IDashboardSegment { 4 | /** 5 | * Returns widgets for all alarms. These should go to the runbook or service dashboard. 6 | */ 7 | alarmWidgets(): IWidget[]; 8 | 9 | /** 10 | * Returns widgets for the summary. These should go to the team OPS dashboard. 11 | */ 12 | summaryWidgets(): IWidget[]; 13 | 14 | /** 15 | * Returns all widgets. These should go to the detailed service dashboard. 16 | */ 17 | widgets(): IWidget[]; 18 | } 19 | -------------------------------------------------------------------------------- /lib/dashboard/DashboardWithBitmapCopy.ts: -------------------------------------------------------------------------------- 1 | import { Dashboard, DashboardProps, IWidget } from "aws-cdk-lib/aws-cloudwatch"; 2 | import { Construct } from "constructs"; 3 | 4 | import { BitmapDashboard } from "./BitmapDashboard"; 5 | 6 | /** 7 | * Composite dashboard which keeps a normal dashboard with its bitmap copy. 8 | * The bitmap copy name will be derived from the primary dashboard name, if specified. 9 | */ 10 | export class DashboardWithBitmapCopy extends Dashboard { 11 | protected readonly bitmapCopy: BitmapDashboard; 12 | 13 | constructor(scope: Construct, id: string, props: DashboardProps) { 14 | super(scope, id, props); 15 | let dashboardName = props.dashboardName; 16 | if (dashboardName !== undefined) { 17 | dashboardName = "Bitmap-" + dashboardName; 18 | } 19 | this.bitmapCopy = new BitmapDashboard(this, "BitmapCopy", { 20 | ...props, 21 | dashboardName, 22 | }); 23 | } 24 | 25 | addWidgets(...widgets: IWidget[]): void { 26 | super.addWidgets(...widgets); 27 | this.bitmapCopy.addWidgets(...widgets); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /lib/dashboard/DefaultWidgetFactory.ts: -------------------------------------------------------------------------------- 1 | import { AlarmWidget, IWidget } from "aws-cdk-lib/aws-cloudwatch"; 2 | 3 | import { IWidgetFactory } from "./IWidgetFactory"; 4 | import { 5 | AlarmWithAnnotation, 6 | DefaultAlarmWidgetHeight, 7 | DefaultAlarmWidgetWidth, 8 | } from "../common"; 9 | 10 | export class DefaultWidgetFactory implements IWidgetFactory { 11 | createAlarmDetailWidget(alarm: AlarmWithAnnotation): IWidget { 12 | return new AlarmWidget({ 13 | alarm: alarm.alarm, 14 | title: alarm.alarmLabel, 15 | width: DefaultAlarmWidgetWidth, 16 | height: DefaultAlarmWidgetHeight, 17 | }); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /lib/dashboard/DynamicDashboardSegment.ts: -------------------------------------------------------------------------------- 1 | import { IWidget } from "aws-cdk-lib/aws-cloudwatch"; 2 | import { DefaultDashboards } from "./DefaultDashboardFactory"; 3 | import { IDashboardFactoryProps } from "./IDashboardFactory"; 4 | 5 | export interface IDynamicDashboardSegment { 6 | /** 7 | * Returns widgets for the requested dashboard type. 8 | * @param name name of dashboard for which widgets are generated. 9 | */ 10 | widgetsForDashboard(name: string): IWidget[]; 11 | } 12 | 13 | export class StaticSegmentDynamicAdapter implements IDynamicDashboardSegment { 14 | protected readonly props: IDashboardFactoryProps; 15 | 16 | constructor(props: IDashboardFactoryProps) { 17 | this.props = props; 18 | } 19 | 20 | /** 21 | * Adapts an IDashboardSegment to the IDynamicDashboardSegment interface by using 22 | * overrideProps to determine if a segment should be shown on a specific dashboard. 23 | * The default values are true, so consumers must set these to false if they would 24 | * like to hide these items from dashboards 25 | */ 26 | widgetsForDashboard(name: string): IWidget[] { 27 | const overrideProps = this.props.overrideProps; 28 | const addToDetailDashboard = overrideProps?.addToDetailDashboard ?? true; 29 | const addToSummaryDashboard = overrideProps?.addToSummaryDashboard ?? true; 30 | const addToAlarmsDashboard = overrideProps?.addToAlarmDashboard ?? true; 31 | if (addToDetailDashboard && name === DefaultDashboards.DETAIL) { 32 | return this.props.segment.widgets(); 33 | } 34 | if (addToSummaryDashboard && name === DefaultDashboards.SUMMARY) { 35 | return this.props.segment.summaryWidgets(); 36 | } 37 | if (addToAlarmsDashboard && name === DefaultDashboards.ALARMS) { 38 | return this.props.segment.alarmWidgets(); 39 | } 40 | return []; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /lib/dashboard/IDashboardFactory.ts: -------------------------------------------------------------------------------- 1 | import { Dashboard } from "aws-cdk-lib/aws-cloudwatch"; 2 | 3 | import { IDashboardSegment } from "./DashboardSegment"; 4 | 5 | export interface MonitoringDashboardsOverrideProps { 6 | /** 7 | * Flag indicating if the widgets should be added to detailed dashboard 8 | * 9 | * @default - true 10 | */ 11 | readonly addToDetailDashboard?: boolean; 12 | /** 13 | * Flag indicating if the widgets should be added to summary dashboard 14 | * 15 | * @default - true 16 | */ 17 | readonly addToSummaryDashboard?: boolean; 18 | /** 19 | * Flag indicating if the widgets should be added to alarm dashboard 20 | * 21 | * @default - true 22 | */ 23 | readonly addToAlarmDashboard?: boolean; 24 | } 25 | 26 | export interface IDashboardFactoryProps { 27 | /** 28 | * Segment to be placed on the dashboard. 29 | */ 30 | segment: IDashboardSegment; 31 | /** 32 | * Dashboard placement override props. 33 | * 34 | * @default - all default 35 | */ 36 | overrideProps?: MonitoringDashboardsOverrideProps; 37 | } 38 | 39 | export interface IDashboardFactory { 40 | addSegment(props: IDashboardFactoryProps): void; 41 | 42 | createdDashboard(): Dashboard | undefined; 43 | 44 | createdSummaryDashboard(): Dashboard | undefined; 45 | 46 | createdAlarmDashboard(): Dashboard | undefined; 47 | } 48 | -------------------------------------------------------------------------------- /lib/dashboard/IDynamicDashboardFactory.ts: -------------------------------------------------------------------------------- 1 | import { Dashboard } from "aws-cdk-lib/aws-cloudwatch"; 2 | import { IDynamicDashboardSegment } from "./DynamicDashboardSegment"; 3 | 4 | /** 5 | * This dashboard factory interface provides for dynamic dashboard generation through 6 | * IDynamicDashboard segments which will return different content depending on the 7 | * dashboard type. 8 | */ 9 | export interface IDynamicDashboardFactory { 10 | /** 11 | * Adds a dynamic dashboard segment. 12 | * @param segment IDynamicDashboardSegment 13 | */ 14 | addDynamicSegment(segment: IDynamicDashboardSegment): void; 15 | 16 | /** 17 | * Gets the dashboard for the requested dashboard type. 18 | * @param type 19 | */ 20 | getDashboard(type: string): Dashboard | undefined; 21 | } 22 | -------------------------------------------------------------------------------- /lib/dashboard/IWidgetFactory.ts: -------------------------------------------------------------------------------- 1 | import { IWidget } from "aws-cdk-lib/aws-cloudwatch"; 2 | 3 | import { AlarmWithAnnotation } from "../common/alarm"; 4 | 5 | /** 6 | * Strategy for creating widgets. 7 | */ 8 | export interface IWidgetFactory { 9 | /** 10 | * Create widget representing an alarm detail. 11 | * @param alarm alarm to represent 12 | */ 13 | createAlarmDetailWidget(alarm: AlarmWithAnnotation): IWidget; 14 | } 15 | -------------------------------------------------------------------------------- /lib/dashboard/SingleWidgetDashboardSegment.ts: -------------------------------------------------------------------------------- 1 | import { IWidget } from "aws-cdk-lib/aws-cloudwatch"; 2 | import { IDashboardSegment } from "./DashboardSegment"; 3 | import { DefaultDashboards } from "./DefaultDashboardFactory"; 4 | import { IDynamicDashboardSegment } from "./DynamicDashboardSegment"; 5 | 6 | export class SingleWidgetDashboardSegment 7 | implements IDashboardSegment, IDynamicDashboardSegment 8 | { 9 | protected readonly widget: IWidget; 10 | protected readonly dashboardsToInclude: string[]; 11 | 12 | /** 13 | * Create a dashboard segment representing a single widget. 14 | * @param widget widget to add 15 | * @param dashboardsToInclude list of dashboard names which to show this widget on. Defaults to the default dashboards. 16 | */ 17 | constructor(widget: IWidget, dashboardsToInclude?: string[]) { 18 | this.widget = widget; 19 | this.dashboardsToInclude = dashboardsToInclude ?? [ 20 | DefaultDashboards.ALARMS, 21 | DefaultDashboards.DETAIL, 22 | DefaultDashboards.SUMMARY, 23 | ]; 24 | } 25 | 26 | widgetsForDashboard(name: string): IWidget[] { 27 | if (this.dashboardsToInclude.includes(name)) { 28 | return [this.widget]; 29 | } else { 30 | return []; 31 | } 32 | } 33 | 34 | alarmWidgets(): IWidget[] { 35 | if (this.dashboardsToInclude.includes(DefaultDashboards.ALARMS)) { 36 | return [this.widget]; 37 | } else { 38 | return []; 39 | } 40 | } 41 | 42 | summaryWidgets(): IWidget[] { 43 | if (this.dashboardsToInclude.includes(DefaultDashboards.SUMMARY)) { 44 | return [this.widget]; 45 | } else { 46 | return []; 47 | } 48 | } 49 | 50 | widgets(): IWidget[] { 51 | if (this.dashboardsToInclude.includes(DefaultDashboards.DETAIL)) { 52 | return [this.widget]; 53 | } else { 54 | return []; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /lib/dashboard/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./BitmapDashboard"; 2 | export * from "./DashboardRenderingPreference"; 3 | export * from "./DynamicDashboardSegment"; 4 | export * from "./DashboardSegment"; 5 | export * from "./DashboardWithBitmapCopy"; 6 | export * from "./DefaultDashboardFactory"; 7 | export * from "./DynamicDashboardFactory"; 8 | export * from "./DefaultWidgetFactory"; 9 | export * from "./IDashboardFactory"; 10 | export * from "./IDynamicDashboardFactory"; 11 | export * from "./IWidgetFactory"; 12 | export * from "./MonitoringNamingStrategy"; 13 | export * from "./SingleWidgetDashboardSegment"; 14 | export * from "./widget"; 15 | -------------------------------------------------------------------------------- /lib/dashboard/widget/AlarmMatrixWidget.ts: -------------------------------------------------------------------------------- 1 | import { AlarmStatusWidget, IAlarm } from "aws-cdk-lib/aws-cloudwatch"; 2 | 3 | import { FullWidth } from "../../common/widget"; 4 | 5 | const AlarmsPerRow = 6; 6 | const MinHeight = 2; 7 | const MaxHeight = 8; 8 | 9 | export interface AlarmMatrixWidgetProps { 10 | /** 11 | * widget title 12 | * @default - no title 13 | */ 14 | readonly title?: string; 15 | /** 16 | * desired height 17 | * @default - auto calculated based on alarm number (3 to 8) 18 | */ 19 | readonly height?: number; 20 | /** 21 | * list of alarms to show 22 | */ 23 | readonly alarms: IAlarm[]; 24 | } 25 | 26 | /** 27 | * Wrapper of Alarm Status Widget which auto-calcultes height based on the number of alarms. 28 | * Always takes the maximum width. 29 | */ 30 | export class AlarmMatrixWidget extends AlarmStatusWidget { 31 | constructor(props: AlarmMatrixWidgetProps) { 32 | super({ 33 | alarms: props.alarms, 34 | title: props.title, 35 | width: FullWidth, 36 | height: 37 | props.height ?? 38 | AlarmMatrixWidget.getRecommendedHeight(props.alarms.length), 39 | }); 40 | } 41 | 42 | private static getRecommendedHeight(numAlarms: number) { 43 | const rows = Math.ceil(numAlarms / AlarmsPerRow); 44 | if (rows < MinHeight) { 45 | return MinHeight; 46 | } 47 | if (rows > MaxHeight) { 48 | return MaxHeight; 49 | } 50 | return rows; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /lib/dashboard/widget/BitmapWidget.ts: -------------------------------------------------------------------------------- 1 | import * as path from "path"; 2 | 3 | import { Duration, Tags } from "aws-cdk-lib"; 4 | import { IWidget } from "aws-cdk-lib/aws-cloudwatch"; 5 | import { Effect, PolicyStatement } from "aws-cdk-lib/aws-iam"; 6 | import { 7 | Code, 8 | determineLatestNodeRuntime, 9 | Function, 10 | IFunction, 11 | } from "aws-cdk-lib/aws-lambda"; 12 | import { RetentionDays } from "aws-cdk-lib/aws-logs"; 13 | import { Construct } from "constructs"; 14 | 15 | import { CustomWidget } from "./CustomWidget"; 16 | 17 | /** 18 | * Support for rendering bitmap widgets on the server side. 19 | * It is a custom widget lambda with some additional roles and helper methods. 20 | */ 21 | export class BitmapWidgetRenderingSupport extends Construct { 22 | readonly handler: IFunction; 23 | 24 | constructor(scope: Construct, id: string) { 25 | super(scope, id); 26 | 27 | this.handler = new Function(this, "Lambda", { 28 | code: Code.fromAsset( 29 | path.join( 30 | __dirname, 31 | "..", 32 | "..", 33 | "..", 34 | "assets", 35 | "BitmapWidgetRenderingSupport", 36 | ), 37 | ), 38 | description: 39 | "Custom Widget Render for Bitmap Widgets (cdk-monitoring-constructs)", 40 | handler: "index.handler", 41 | memorySize: 128, 42 | runtime: determineLatestNodeRuntime(this), 43 | timeout: Duration.seconds(60), 44 | logRetention: RetentionDays.ONE_DAY, 45 | }); 46 | 47 | this.handler.addToRolePolicy( 48 | new PolicyStatement({ 49 | actions: ["cloudwatch:GetMetricWidgetImage"], 50 | effect: Effect.ALLOW, 51 | resources: ["*"], 52 | }), 53 | ); 54 | 55 | Tags.of(this.handler).add("cw-custom-widget", "describe:readOnly"); 56 | } 57 | 58 | asBitmap(widget: IWidget) { 59 | const props = this.getWidgetProperties(widget); 60 | // remove the title from the graph and remember it 61 | const { title, ...graph } = props; 62 | 63 | return new CustomWidget({ 64 | // move the original title here 65 | title, 66 | width: widget.width, 67 | height: widget.height, 68 | // empty the inner title since we already have it on the whole widget 69 | handlerParams: { graph: { ...graph, title: " " } }, 70 | handler: this.handler, 71 | updateOnRefresh: true, 72 | updateOnResize: true, 73 | updateOnTimeRangeChange: true, 74 | }); 75 | } 76 | 77 | protected getWidgetProperties(widget: IWidget): any { 78 | const graphs = widget.toJson(); 79 | if (graphs.length != 1) { 80 | throw new Error( 81 | "Number of objects in the widget definition must be exactly one.", 82 | ); 83 | } 84 | const graph = graphs[0]; 85 | if (!graph.properties) { 86 | throw new Error("No graph properties: " + graph); 87 | } 88 | return graph.properties; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /lib/dashboard/widget/CustomWidget.ts: -------------------------------------------------------------------------------- 1 | import { ConcreteWidget } from "aws-cdk-lib/aws-cloudwatch"; 2 | import { IFunction } from "aws-cdk-lib/aws-lambda"; 3 | 4 | /** 5 | * Properties of a custom widget. 6 | */ 7 | export interface CustomWidgetProps { 8 | /** 9 | * Title for the graph 10 | */ 11 | readonly title?: string; 12 | /** 13 | * Width of the widget, in a grid of 24 units wide 14 | * 15 | * @default - 6 16 | */ 17 | readonly width?: number; 18 | /** 19 | * Height of the widget 20 | * 21 | * @default - 6 22 | */ 23 | readonly height?: number; 24 | /** 25 | * Lambda providing the widget contents. 26 | * The Lambda function should return HTML with widget code. 27 | * The simplest Lambda example: 28 | * ```typescript 29 | * exports.handler = function (event, context, callback) { 30 | * return callback(null, "

Hello! This is a custom widget.

" + JSON.stringify(event, null, 2) + "
"); 31 | * }; 32 | * ``` 33 | */ 34 | readonly handler: IFunction; 35 | /** 36 | * Arguments to pass to the Lambda. 37 | * These arguments will be available in the Lambda context. 38 | */ 39 | readonly handlerParams?: any; 40 | /** 41 | * Whether the widget should be updated (by calling the Lambda again) on refresh. 42 | * 43 | * @default - true 44 | */ 45 | readonly updateOnRefresh?: boolean; 46 | /** 47 | * Whether the widget should be updated (by calling the Lambda again) on resize. 48 | * 49 | * @default - true 50 | */ 51 | readonly updateOnResize?: boolean; 52 | /** 53 | * Whether the widget should be updated (by calling the Lambda again) on time range change. 54 | * 55 | * @default - true 56 | */ 57 | readonly updateOnTimeRangeChange?: boolean; 58 | } 59 | 60 | /** 61 | * A dashboard widget that can be customized using a Lambda. 62 | */ 63 | export class CustomWidget extends ConcreteWidget { 64 | private readonly props: CustomWidgetProps; 65 | 66 | constructor(props: CustomWidgetProps) { 67 | super(props.width || 6, props.height || 6); 68 | this.props = props; 69 | } 70 | 71 | toJson(): any[] { 72 | return [ 73 | { 74 | type: "custom", 75 | width: this.width, 76 | height: this.height, 77 | x: this.x, 78 | y: this.y, 79 | properties: { 80 | title: this.props.title, 81 | endpoint: this.props.handler.functionArn, 82 | params: this.props.handlerParams || {}, 83 | updateOn: { 84 | refresh: this.props.updateOnRefresh ?? true, 85 | resize: this.props.updateOnResize ?? true, 86 | timeRange: this.props.updateOnTimeRangeChange ?? true, 87 | }, 88 | }, 89 | }, 90 | ]; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /lib/dashboard/widget/HeaderWidget.ts: -------------------------------------------------------------------------------- 1 | import { TextWidget } from "aws-cdk-lib/aws-cloudwatch"; 2 | 3 | import { FullWidth } from "../../common/widget"; 4 | 5 | export enum HeaderLevel { 6 | LARGE, 7 | MEDIUM, 8 | SMALL, 9 | } 10 | 11 | export class HeaderWidget extends TextWidget { 12 | constructor( 13 | text: string, 14 | level?: HeaderLevel, 15 | description?: string, 16 | descriptionHeight?: number, 17 | ) { 18 | super({ 19 | width: FullWidth, 20 | height: HeaderWidget.calculateHeight(description, descriptionHeight), 21 | markdown: HeaderWidget.toMarkdown( 22 | text, 23 | level ?? HeaderLevel.LARGE, 24 | description, 25 | ), 26 | }); 27 | } 28 | 29 | private static calculateHeight( 30 | description?: string, 31 | descriptionHeight?: number, 32 | ) { 33 | let result = 1; 34 | if (description) { 35 | result += descriptionHeight ?? 1; 36 | } 37 | return result; 38 | } 39 | 40 | private static toMarkdown( 41 | text: string, 42 | level: HeaderLevel, 43 | description?: string, 44 | ) { 45 | const parts = [this.toHeaderMarkdown(text, level)]; 46 | if (description) { 47 | parts.push(description); 48 | } 49 | return parts.join("\n\n"); 50 | } 51 | 52 | private static toHeaderMarkdown(text: string, level: HeaderLevel) { 53 | return "#".repeat(level + 1) + " " + text; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /lib/dashboard/widget/KeyValueTableWidget.ts: -------------------------------------------------------------------------------- 1 | import { KeyValueTableWidgetV2 } from "./KeyValueTableWidgetV2"; 2 | 3 | /** 4 | * @deprecated Use {@link KeyValueTableWidgetV2} instead. 5 | */ 6 | export class KeyValueTableWidget extends KeyValueTableWidgetV2 { 7 | constructor(data: [string, string][]) { 8 | super(data.map(([key, value]) => ({ key, value }))); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /lib/dashboard/widget/KeyValueTableWidgetV2.ts: -------------------------------------------------------------------------------- 1 | import { TextWidget } from "aws-cdk-lib/aws-cloudwatch"; 2 | 3 | import { FullWidth } from "../../common/widget"; 4 | 5 | export interface KeyValue { 6 | readonly key: string; 7 | readonly value: string; 8 | } 9 | 10 | export class KeyValueTableWidgetV2 extends TextWidget { 11 | constructor(data: KeyValue[]) { 12 | super({ 13 | width: FullWidth, 14 | height: 3, 15 | markdown: KeyValueTableWidgetV2.toMarkdown(data), 16 | }); 17 | } 18 | 19 | private static toMarkdown(data: KeyValue[]) { 20 | let headerRow = ""; 21 | let subHeaderRow = ""; 22 | let valueRow = ""; 23 | 24 | data.forEach(({ key, value }) => { 25 | headerRow += "| " + key; 26 | subHeaderRow += "|---"; 27 | valueRow += "| " + value; 28 | }); 29 | 30 | return `${headerRow}\n${subHeaderRow}\n${valueRow}`; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /lib/dashboard/widget/MonitoringHeaderWidget.ts: -------------------------------------------------------------------------------- 1 | import { HeaderLevel, HeaderWidget } from "./HeaderWidget"; 2 | 3 | export interface MonitoringHeaderWidgetProps { 4 | readonly title: string; 5 | readonly family?: string; 6 | readonly goToLinkUrl?: string; 7 | readonly description?: string; 8 | readonly descriptionHeight?: number; 9 | } 10 | 11 | export class MonitoringHeaderWidget extends HeaderWidget { 12 | constructor(props: MonitoringHeaderWidgetProps) { 13 | super( 14 | MonitoringHeaderWidget.getText(props), 15 | HeaderLevel.SMALL, 16 | props.description, 17 | props.descriptionHeight, 18 | ); 19 | } 20 | 21 | private static getText(props: MonitoringHeaderWidgetProps): string { 22 | let title = props.title; 23 | 24 | if (props.goToLinkUrl) { 25 | title = `[${title}](${props.goToLinkUrl})`; 26 | } 27 | 28 | if (props.family) { 29 | title = `${props.family} **${title}**`; 30 | } 31 | 32 | return title; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/dashboard/widget/UnofficialWidgets.ts: -------------------------------------------------------------------------------- 1 | import { ConcreteWidget, MetricWidgetProps } from "aws-cdk-lib/aws-cloudwatch"; 2 | 3 | import { FullWidth } from "../../common/widget"; 4 | 5 | export interface AlarmSummaryMatrixWidgetProps extends MetricWidgetProps { 6 | readonly alarmArns: string[]; 7 | } 8 | 9 | export interface AlarmSummaryMatrixWidgetPropertiesJson { 10 | readonly title?: string; 11 | readonly alarms: string[]; 12 | } 13 | 14 | export class AlarmSummaryMatrixWidget extends ConcreteWidget { 15 | protected readonly props: AlarmSummaryMatrixWidgetProps; 16 | 17 | constructor(props: AlarmSummaryMatrixWidgetProps) { 18 | super(props.width ?? FullWidth, props.height ?? 2); 19 | this.props = props; 20 | } 21 | 22 | toJson(): any[] { 23 | return [ 24 | { 25 | type: "alarm", 26 | width: this.width, 27 | height: this.height, 28 | x: this.x, 29 | y: this.y, 30 | properties: { 31 | title: this.props.title, 32 | alarms: this.props.alarmArns, 33 | }, 34 | }, 35 | ]; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /lib/dashboard/widget/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./AlarmMatrixWidget"; 2 | export * from "./BitmapWidget"; 3 | export * from "./CustomWidget"; 4 | export * from "./HeaderWidget"; 5 | export * from "./KeyValueTableWidget"; 6 | export * from "./KeyValueTableWidgetV2"; 7 | export * from "./MonitoringHeaderWidget"; 8 | export * from "./StrictGraphWidget"; 9 | export * from "./UnofficialWidgets"; 10 | -------------------------------------------------------------------------------- /lib/facade/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./IMonitoringAspect"; 2 | export * from "./MonitoringFacade"; 3 | export * from "./MonitoringAspect"; 4 | -------------------------------------------------------------------------------- /lib/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./common"; 2 | export * from "./dashboard"; 3 | export * from "./facade"; 4 | export * from "./monitoring"; 5 | -------------------------------------------------------------------------------- /lib/monitoring/aws-acm/CertificateManagerMetricFactory.ts: -------------------------------------------------------------------------------- 1 | import { ICertificate } from "aws-cdk-lib/aws-certificatemanager"; 2 | import { DimensionsMap } from "aws-cdk-lib/aws-cloudwatch"; 3 | 4 | import { 5 | BaseMetricFactory, 6 | BaseMetricFactoryProps, 7 | MetricFactory, 8 | MetricStatistic, 9 | MetricWithAlarmSupport, 10 | } from "../../common"; 11 | 12 | const Namespace = "AWS/CertificateManager"; 13 | 14 | export interface CertificateManagerMetricFactoryProps 15 | extends BaseMetricFactoryProps { 16 | readonly certificate: ICertificate; 17 | } 18 | 19 | export class CertificateManagerMetricFactory extends BaseMetricFactory { 20 | protected readonly dimensionsMap: DimensionsMap; 21 | 22 | constructor( 23 | metricFactory: MetricFactory, 24 | props: CertificateManagerMetricFactoryProps, 25 | ) { 26 | super(metricFactory, props); 27 | 28 | this.dimensionsMap = { 29 | CertificateArn: props.certificate.certificateArn, 30 | }; 31 | } 32 | 33 | metricDaysToExpiry(): MetricWithAlarmSupport { 34 | return this.metricFactory.createMetric( 35 | "DaysToExpiry", 36 | MetricStatistic.MIN, 37 | "Days to expiry", 38 | this.dimensionsMap, 39 | undefined, 40 | Namespace, 41 | undefined, 42 | this.region, 43 | this.account, 44 | ); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /lib/monitoring/aws-acm/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./CertificateManagerMetricFactory"; 2 | export * from "./CertificateManagerMonitoring"; 3 | -------------------------------------------------------------------------------- /lib/monitoring/aws-apigateway/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./ApiGatewayMetricFactory"; 2 | export * from "./ApiGatewayMonitoring"; 3 | -------------------------------------------------------------------------------- /lib/monitoring/aws-apigatewayv2/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./ApiGatewayV2HttpApiMetricFactory"; 2 | export * from "./ApiGatewayV2HttpApiMonitoring"; 3 | -------------------------------------------------------------------------------- /lib/monitoring/aws-appsync/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./AppSyncMetricFactory"; 2 | export * from "./AppSyncMonitoring"; 3 | -------------------------------------------------------------------------------- /lib/monitoring/aws-billing/BillingMetricFactory.ts: -------------------------------------------------------------------------------- 1 | import { Duration } from "aws-cdk-lib"; 2 | import { IMetric, MathExpression, Metric } from "aws-cdk-lib/aws-cloudwatch"; 3 | 4 | import { MetricStatistic, MetricWithAlarmSupport } from "../../common"; 5 | 6 | export const BillingRegion = "us-east-1"; 7 | export const BillingCurrency = "USD"; 8 | 9 | const BillingNamespace = "AWS/Billing"; 10 | const BillingMetric = "EstimatedCharges"; 11 | const BillingPeriod = Duration.days(1); 12 | const DefaultServiceLimit = 10; 13 | 14 | export class BillingMetricFactory { 15 | metricSearchTopCostByServiceInUsd(): IMetric { 16 | const search = new MathExpression({ 17 | period: BillingPeriod, 18 | searchRegion: BillingRegion, 19 | expression: `SEARCH('{${BillingNamespace},Currency,ServiceName} MetricName="${BillingMetric}"', 'Maximum', ${BillingPeriod.toSeconds()})`, 20 | usingMetrics: {}, 21 | label: " ", 22 | }); 23 | 24 | return new MathExpression({ 25 | period: BillingPeriod, 26 | searchRegion: BillingRegion, 27 | expression: `SORT(search, MAX, DESC, ${DefaultServiceLimit})`, 28 | usingMetrics: { search }, 29 | label: " ", 30 | }); 31 | } 32 | 33 | metricTotalCostInUsd(): MetricWithAlarmSupport { 34 | // not using metric factory because we customize everything 35 | return new Metric({ 36 | namespace: BillingNamespace, 37 | metricName: BillingMetric, 38 | dimensionsMap: { Currency: BillingCurrency }, 39 | period: BillingPeriod, 40 | label: `Estimated Charges`, 41 | region: BillingRegion, 42 | statistic: MetricStatistic.MAX, 43 | }); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /lib/monitoring/aws-billing/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./BillingMetricFactory"; 2 | export * from "./BillingMonitoring"; 3 | -------------------------------------------------------------------------------- /lib/monitoring/aws-cloudfront/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./CloudFrontDistributionMetricFactory"; 2 | export * from "./CloudFrontDistributionMonitoring"; 3 | -------------------------------------------------------------------------------- /lib/monitoring/aws-cloudwatch/CloudWatchLogsMetricFactory.ts: -------------------------------------------------------------------------------- 1 | import { DimensionsMap } from "aws-cdk-lib/aws-cloudwatch"; 2 | 3 | import { 4 | BaseMetricFactory, 5 | BaseMetricFactoryProps, 6 | MetricFactory, 7 | MetricStatistic, 8 | } from "../../common"; 9 | 10 | const CloudWatchLogsNamespace = "AWS/Logs"; 11 | 12 | export interface CloudWatchLogsMetricFactoryProps 13 | extends BaseMetricFactoryProps { 14 | /** 15 | * Name of the log group to monitor. 16 | */ 17 | readonly logGroupName: string; 18 | } 19 | 20 | export class CloudWatchLogsMetricFactory extends BaseMetricFactory { 21 | private readonly dimensionsMap: DimensionsMap; 22 | 23 | constructor( 24 | metricFactory: MetricFactory, 25 | props: CloudWatchLogsMetricFactoryProps, 26 | ) { 27 | super(metricFactory, props); 28 | 29 | this.dimensionsMap = { 30 | LogGroupName: props.logGroupName, 31 | }; 32 | } 33 | 34 | metricIncomingLogEvents() { 35 | return this.metricFactory.createMetric( 36 | "IncomingLogEvents", 37 | MetricStatistic.SUM, 38 | "Logs", 39 | this.dimensionsMap, 40 | undefined, 41 | CloudWatchLogsNamespace, 42 | undefined, 43 | this.region, 44 | this.account, 45 | ); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /lib/monitoring/aws-cloudwatch/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./CloudWatchLogsMetricFactory"; 2 | export * from "./LogMonitoring"; 3 | -------------------------------------------------------------------------------- /lib/monitoring/aws-codebuild/CodeBuildProjectMetricFactory.ts: -------------------------------------------------------------------------------- 1 | import { DimensionsMap } from "aws-cdk-lib/aws-cloudwatch"; 2 | import { IProject } from "aws-cdk-lib/aws-codebuild"; 3 | 4 | import { 5 | BaseMetricFactory, 6 | BaseMetricFactoryProps, 7 | MetricFactory, 8 | MetricStatistic, 9 | } from "../../common"; 10 | 11 | export interface CodeBuildProjectMetricFactoryProps 12 | extends BaseMetricFactoryProps { 13 | readonly project: IProject; 14 | } 15 | 16 | export class CodeBuildProjectMetricFactory extends BaseMetricFactory { 17 | protected readonly dimensionsMap: DimensionsMap; 18 | protected readonly project: IProject; 19 | 20 | constructor( 21 | metricFactory: MetricFactory, 22 | props: CodeBuildProjectMetricFactoryProps, 23 | ) { 24 | super(metricFactory, props); 25 | 26 | this.project = props.project; 27 | this.dimensionsMap = { 28 | ProjectName: props.project.projectName, 29 | }; 30 | } 31 | 32 | metricBuildCount() { 33 | return this.metricFactory.adaptMetric( 34 | this.project.metricBuilds({ 35 | region: this.region, 36 | account: this.account, 37 | }), 38 | ); 39 | } 40 | 41 | metricSucceededBuildCount() { 42 | return this.metricFactory.adaptMetric( 43 | this.project.metricSucceededBuilds({ 44 | region: this.region, 45 | account: this.account, 46 | }), 47 | ); 48 | } 49 | 50 | metricFailedBuildCount() { 51 | return this.metricFactory.adaptMetric( 52 | this.project.metricFailedBuilds({ 53 | region: this.region, 54 | account: this.account, 55 | }), 56 | ); 57 | } 58 | 59 | metricFailedBuildRate() { 60 | return this.metricFailedBuildCount().with({ 61 | statistic: MetricStatistic.AVERAGE, 62 | }); 63 | } 64 | 65 | metricDurationP99InSeconds() { 66 | return this.metricFactory.adaptMetric( 67 | this.project.metricDuration({ 68 | label: "P99", 69 | statistic: MetricStatistic.P99, 70 | region: this.region, 71 | account: this.account, 72 | }), 73 | ); 74 | } 75 | 76 | metricDurationP90InSeconds() { 77 | return this.metricFactory.adaptMetric( 78 | this.project.metricDuration({ 79 | label: "P90", 80 | statistic: MetricStatistic.P90, 81 | region: this.region, 82 | account: this.account, 83 | }), 84 | ); 85 | } 86 | 87 | metricDurationP50InSeconds() { 88 | return this.metricFactory.adaptMetric( 89 | this.project.metricDuration({ 90 | label: "P50", 91 | statistic: MetricStatistic.P50, 92 | region: this.region, 93 | account: this.account, 94 | }), 95 | ); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /lib/monitoring/aws-codebuild/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./CodeBuildProjectMetricFactory"; 2 | export * from "./CodeBuildProjectMonitoring"; 3 | -------------------------------------------------------------------------------- /lib/monitoring/aws-docdb/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./DocumentDbMetricFactory"; 2 | export * from "./DocumentDbMonitoring"; 3 | -------------------------------------------------------------------------------- /lib/monitoring/aws-dynamo/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./DynamoTableGlobalSecondaryIndexMetricFactory"; 2 | export * from "./DynamoTableGlobalSecondaryIndexMonitoring"; 3 | export * from "./DynamoTableMetricFactory"; 4 | export * from "./DynamoTableMonitoring"; 5 | -------------------------------------------------------------------------------- /lib/monitoring/aws-ec2/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./AutoScalingGroupMetricFactory"; 2 | export * from "./AutoScalingGroupMonitoring"; 3 | export * from "./EC2MetricFactory"; 4 | export * from "./EC2Monitoring"; 5 | -------------------------------------------------------------------------------- /lib/monitoring/aws-ecs-patterns/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./BaseServiceMetricFactory"; 2 | export * from "./Ec2ServiceMonitoring"; 3 | export * from "./FargateServiceMonitoring"; 4 | export * from "./misc"; 5 | -------------------------------------------------------------------------------- /lib/monitoring/aws-ecs-patterns/misc.ts: -------------------------------------------------------------------------------- 1 | import { 2 | QueueProcessingEc2Service, 3 | QueueProcessingFargateService, 4 | } from "aws-cdk-lib/aws-ecs-patterns"; 5 | import { IQueue } from "aws-cdk-lib/aws-sqs"; 6 | 7 | import { Ec2ServiceMonitoring } from "./Ec2ServiceMonitoring"; 8 | import { 9 | BaseFargateServiceAlarms, 10 | FargateServiceMonitoring, 11 | } from "./FargateServiceMonitoring"; 12 | import { BaseMonitoringProps, MonitoringScope } from "../../common"; 13 | import { 14 | BaseDlqAlarms, 15 | BaseSqsQueueAlarms, 16 | SqsQueueMonitoring, 17 | SqsQueueMonitoringWithDlq, 18 | } from "../aws-sqs"; 19 | 20 | interface BaseQueueProcessingServiceMonitoringProps 21 | extends BaseMonitoringProps { 22 | readonly addServiceAlarms?: BaseFargateServiceAlarms; 23 | readonly addQueueAlarms?: BaseSqsQueueAlarms; 24 | readonly addDeadLetterQueueAlarms?: BaseDlqAlarms; 25 | } 26 | 27 | export interface QueueProcessingFargateServiceMonitoringProps 28 | extends BaseQueueProcessingServiceMonitoringProps { 29 | readonly fargateService: QueueProcessingFargateService; 30 | } 31 | 32 | export interface QueueProcessingEc2ServiceMonitoringProps 33 | extends BaseQueueProcessingServiceMonitoringProps { 34 | readonly ec2Service: QueueProcessingEc2Service; 35 | } 36 | 37 | export function getQueueProcessingFargateServiceMonitoring( 38 | facade: MonitoringScope, 39 | props: QueueProcessingFargateServiceMonitoringProps, 40 | ) { 41 | return [ 42 | new FargateServiceMonitoring(facade, { 43 | ...props, 44 | fargateService: props.fargateService.service, 45 | ...props.addServiceAlarms, 46 | }), 47 | getCommonQueueProcessingMonitoring( 48 | facade, 49 | props, 50 | props.fargateService.sqsQueue, 51 | props.fargateService.deadLetterQueue, 52 | ), 53 | ]; 54 | } 55 | 56 | export function getQueueProcessingEc2ServiceMonitoring( 57 | facade: MonitoringScope, 58 | props: QueueProcessingEc2ServiceMonitoringProps, 59 | ) { 60 | return [ 61 | new Ec2ServiceMonitoring(facade, { 62 | ...props, 63 | ec2Service: props.ec2Service.service, 64 | ...props.addServiceAlarms, 65 | }), 66 | getCommonQueueProcessingMonitoring( 67 | facade, 68 | props, 69 | props.ec2Service.sqsQueue, 70 | props.ec2Service.deadLetterQueue, 71 | ), 72 | ]; 73 | } 74 | 75 | function getCommonQueueProcessingMonitoring( 76 | scope: MonitoringScope, 77 | props: BaseQueueProcessingServiceMonitoringProps, 78 | queue: IQueue, 79 | deadLetterQueue?: IQueue, 80 | ) { 81 | if (deadLetterQueue) { 82 | return new SqsQueueMonitoringWithDlq(scope, { 83 | ...props, 84 | queue, 85 | deadLetterQueue: deadLetterQueue!, 86 | ...props.addQueueAlarms, 87 | ...props.addDeadLetterQueueAlarms, 88 | }); 89 | } else { 90 | return new SqsQueueMonitoring(scope, { 91 | ...props, 92 | queue, 93 | ...props.addDeadLetterQueueAlarms, 94 | }); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /lib/monitoring/aws-elasticache/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./ElastiCacheClusterMetricFactory"; 2 | export * from "./ElastiCacheClusterMonitoring"; 3 | -------------------------------------------------------------------------------- /lib/monitoring/aws-glue/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./GlueJobMetricFactory"; 2 | export * from "./GlueJobMonitoring"; 3 | -------------------------------------------------------------------------------- /lib/monitoring/aws-kinesis/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./KinesisDataStreamMetricFactory"; 2 | export * from "./KinesisFirehoseMetricFactory"; 3 | export * from "./KinesisDataStreamMonitoring"; 4 | export * from "./KinesisFirehoseMonitoring"; 5 | -------------------------------------------------------------------------------- /lib/monitoring/aws-kinesisanalytics/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./KinesisDataAnalyticsMetricFactory"; 2 | export * from "./KinesisDataAnalyticsMonitoring"; 3 | -------------------------------------------------------------------------------- /lib/monitoring/aws-lambda/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./LambdaFunctionEnhancedMetricFactory"; 2 | export * from "./LambdaFunctionMetricFactory"; 3 | export * from "./LambdaFunctionMonitoring"; 4 | -------------------------------------------------------------------------------- /lib/monitoring/aws-loadbalancing/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./ApplicationLoadBalancerMetricFactory"; 2 | export * from "./LoadBalancerMetricFactory"; 3 | export * from "./NetworkLoadBalancerMetricFactory"; 4 | export * from "./NetworkLoadBalancerMonitoring"; 5 | -------------------------------------------------------------------------------- /lib/monitoring/aws-opensearch/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./OpenSearchBackportedMetrics"; 2 | export * from "./OpenSearchClusterMetricFactory"; 3 | export * from "./OpenSearchClusterMonitoring"; 4 | -------------------------------------------------------------------------------- /lib/monitoring/aws-rds/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./AuroraClusterMonitoring"; 2 | export * from "./RdsClusterMetricFactory"; 3 | export * from "./RdsClusterMonitoring"; 4 | export * from "./RdsInstanceMetricFactory"; 5 | export * from "./RdsInstanceMonitoring"; 6 | -------------------------------------------------------------------------------- /lib/monitoring/aws-redshift/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./RedshiftClusterMetricFactory"; 2 | export * from "./RedshiftClusterMonitoring"; 3 | -------------------------------------------------------------------------------- /lib/monitoring/aws-s3/S3BucketMetricFactory.ts: -------------------------------------------------------------------------------- 1 | import { IBucket } from "aws-cdk-lib/aws-s3"; 2 | 3 | import { 4 | BaseMetricFactory, 5 | BaseMetricFactoryProps, 6 | MetricFactory, 7 | MetricStatistic, 8 | } from "../../common"; 9 | 10 | const Namespace = "AWS/S3"; 11 | 12 | export enum StorageType { 13 | DEEP_ARCHIVE_OBJECT_OVERHEAD = "DeepArchiveObjectOverhead", 14 | DEEP_ARCHIVE_S3_OBJECT_OVERHEAD = "DeepArchiveS3ObjectOverhead", 15 | DEEP_ARCHIVE_STAGING_STORAGE = "DeepArchiveStagingStorage", 16 | DEEP_ARCHIVE_STORAGE = "DeepArchiveStorage", 17 | GLACIER_OBJECT_OVERHEAD = "GlacierObjectOverhead", 18 | GLACIER_S3_OBJECT_OVERHEAD = "GlacierS3ObjectOverhead", 19 | GLACIER_STAGING_STORAGE = "GlacierStagingStorage", 20 | GLACIER_STORAGE = "GlacierStorage", 21 | INTELLIGENT_TIERING_FA_STORAGE = "IntelligentTieringFAStorage", 22 | INTELLIGENT_TIERING_IA_STORAGE = "IntelligentTieringIAStorage", 23 | ONE_ZONE_IA_SIZE_OVERHEAD = "OneZoneIASizeOverhead", 24 | ONE_ZONE_IA_STORAGE = "OneZoneIAStorage", 25 | REDUCED_REDUNDANCY_STORAGE = "ReducedRedundancyStorage", 26 | STANDARD_IA_SIZE_OVERHEAD = "StandardIASizeOverhead", 27 | STANDARD_IA_STORAGE = "StandardIAStorage", 28 | STANDARD_STORAGE = "StandardStorage", 29 | } 30 | 31 | export interface S3BucketMetricFactoryProps extends BaseMetricFactoryProps { 32 | readonly bucket: IBucket; 33 | readonly storageType?: StorageType; 34 | } 35 | 36 | export class S3BucketMetricFactory extends BaseMetricFactory { 37 | protected readonly props: S3BucketMetricFactoryProps; 38 | 39 | constructor(metricFactory: MetricFactory, props: S3BucketMetricFactoryProps) { 40 | super(metricFactory, props); 41 | 42 | this.props = props; 43 | } 44 | 45 | metricBucketSizeBytes() { 46 | return this.metricFactory.createMetric( 47 | "BucketSizeBytes", 48 | MetricStatistic.AVERAGE, 49 | "BucketSizeBytes", 50 | { 51 | BucketName: this.props.bucket.bucketName, 52 | StorageType: this.props.storageType ?? StorageType.STANDARD_STORAGE, 53 | }, 54 | undefined, 55 | Namespace, 56 | undefined, 57 | this.region, 58 | this.account, 59 | ); 60 | } 61 | 62 | metricNumberOfObjects() { 63 | return this.metricFactory.createMetric( 64 | "NumberOfObjects", 65 | MetricStatistic.AVERAGE, 66 | "NumberOfObjects", 67 | { 68 | BucketName: this.props.bucket.bucketName, 69 | StorageType: "AllStorageTypes", 70 | }, 71 | undefined, 72 | Namespace, 73 | undefined, 74 | this.region, 75 | this.account, 76 | ); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /lib/monitoring/aws-s3/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./S3BucketMetricFactory"; 2 | export * from "./S3BucketMonitoring"; 3 | -------------------------------------------------------------------------------- /lib/monitoring/aws-secretsmanager/SecretsManagerMetricFactory.ts: -------------------------------------------------------------------------------- 1 | import { Duration } from "aws-cdk-lib"; 2 | import { 3 | BaseMetricFactory, 4 | BaseMetricFactoryProps, 5 | MetricFactory, 6 | MetricStatistic, 7 | } from "../../common"; 8 | 9 | const CLASS = "None"; 10 | const DEFAULT_METRIC_PERIOD = Duration.hours(1); 11 | const METRICNAMESECRETCOUNT = "ResourceCount"; 12 | const NAMESPACE = "AWS/SecretsManager"; 13 | const RESOURCE = "SecretCount"; 14 | const SERVICE = "Secrets Manager"; 15 | const TYPE = "Resource"; 16 | 17 | export type SecretsManagerMetricFactoryProps = BaseMetricFactoryProps; 18 | 19 | export class SecretsManagerMetricFactory extends BaseMetricFactory { 20 | constructor( 21 | metricFactory: MetricFactory, 22 | props: SecretsManagerMetricFactoryProps, 23 | ) { 24 | super(metricFactory, props); 25 | } 26 | 27 | metricSecretCount() { 28 | const dimensionsMap = { 29 | Class: CLASS, 30 | Resource: RESOURCE, 31 | Service: SERVICE, 32 | Type: TYPE, 33 | }; 34 | 35 | return this.metricFactory.createMetric( 36 | METRICNAMESECRETCOUNT, 37 | MetricStatistic.AVERAGE, 38 | "Count", 39 | dimensionsMap, 40 | undefined, 41 | NAMESPACE, 42 | DEFAULT_METRIC_PERIOD, 43 | this.region, 44 | this.account, 45 | ); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /lib/monitoring/aws-secretsmanager/SecretsManagerSecretMetricFactory.ts: -------------------------------------------------------------------------------- 1 | import { DimensionsMap } from "aws-cdk-lib/aws-cloudwatch"; 2 | import { ISecret } from "aws-cdk-lib/aws-secretsmanager"; 3 | 4 | import { 5 | BaseMetricFactory, 6 | BaseMetricFactoryProps, 7 | MetricFactory, 8 | MetricStatistic, 9 | } from "../../common"; 10 | 11 | export interface SecretsManagerSecretMetricFactoryProps 12 | extends BaseMetricFactoryProps { 13 | readonly secret: ISecret; 14 | } 15 | 16 | export class SecretsManagerSecretMetricFactory extends BaseMetricFactory { 17 | static readonly Namespace = "SecretsManager"; 18 | static readonly MetricNameDaysSinceLastChange = "DaysSinceLastChange"; 19 | static readonly MetricNameDaysSinceLastRotation = "DaysSinceLastRotation"; 20 | protected readonly dimensionsMap: DimensionsMap; 21 | protected readonly secret: ISecret; 22 | 23 | constructor( 24 | metricFactory: MetricFactory, 25 | props: SecretsManagerSecretMetricFactoryProps, 26 | ) { 27 | super(metricFactory, props); 28 | 29 | this.secret = props.secret; 30 | this.dimensionsMap = { 31 | SecretName: props.secret.secretName, 32 | }; 33 | } 34 | 35 | metricDaysSinceLastChange() { 36 | return this.metricFactory.createMetric( 37 | SecretsManagerSecretMetricFactory.MetricNameDaysSinceLastChange, 38 | MetricStatistic.MAX, 39 | "Days", 40 | this.dimensionsMap, 41 | undefined, 42 | SecretsManagerSecretMetricFactory.Namespace, 43 | undefined, 44 | this.region, 45 | this.account, 46 | ); 47 | } 48 | 49 | metricDaysSinceLastRotation() { 50 | return this.metricFactory.createMetric( 51 | SecretsManagerSecretMetricFactory.MetricNameDaysSinceLastRotation, 52 | MetricStatistic.MAX, 53 | "Days", 54 | this.dimensionsMap, 55 | undefined, 56 | SecretsManagerSecretMetricFactory.Namespace, 57 | undefined, 58 | this.region, 59 | this.account, 60 | ); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /lib/monitoring/aws-secretsmanager/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./SecretsManagerMetricFactory"; 2 | export * from "./SecretsManagerMetricsPublisher"; 3 | export * from "./SecretsManagerSecretMetricFactory"; 4 | export * from "./SecretsManagerMonitoring"; 5 | export * from "./SecretsManagerSecretMonitoring"; 6 | -------------------------------------------------------------------------------- /lib/monitoring/aws-sns/SnsTopicMetricFactory.ts: -------------------------------------------------------------------------------- 1 | import { ITopic } from "aws-cdk-lib/aws-sns"; 2 | 3 | import { 4 | BaseMetricFactory, 5 | BaseMetricFactoryProps, 6 | MetricFactory, 7 | } from "../../common"; 8 | 9 | export interface SnsTopicMetricFactoryProps extends BaseMetricFactoryProps { 10 | readonly topic: ITopic; 11 | } 12 | 13 | export class SnsTopicMetricFactory extends BaseMetricFactory { 14 | protected readonly topic: ITopic; 15 | 16 | constructor(metricFactory: MetricFactory, props: SnsTopicMetricFactoryProps) { 17 | super(metricFactory, props); 18 | 19 | this.topic = props.topic; 20 | } 21 | 22 | metricIncomingMessageCount() { 23 | return this.metricFactory.adaptMetric( 24 | this.topic.metricNumberOfMessagesPublished({ 25 | label: "Incoming", 26 | region: this.region, 27 | account: this.account, 28 | }), 29 | ); 30 | } 31 | 32 | metricOutgoingMessageCount() { 33 | return this.metricFactory.adaptMetric( 34 | this.topic.metricNumberOfNotificationsDelivered({ 35 | label: "Outgoing", 36 | region: this.region, 37 | account: this.account, 38 | }), 39 | ); 40 | } 41 | 42 | metricAverageMessageSizeInBytes() { 43 | return this.metricFactory.adaptMetric( 44 | this.topic.metricPublishSize({ 45 | label: "Size", 46 | region: this.region, 47 | account: this.account, 48 | }), 49 | ); 50 | } 51 | 52 | metricNumberOfNotificationsFailed() { 53 | return this.metricFactory.adaptMetric( 54 | this.topic.metricNumberOfNotificationsFailed({ 55 | label: "Failed", 56 | region: this.region, 57 | account: this.account, 58 | }), 59 | ); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /lib/monitoring/aws-sns/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./SnsTopicMetricFactory"; 2 | export * from "./SnsTopicMonitoring"; 3 | -------------------------------------------------------------------------------- /lib/monitoring/aws-sqs/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./SqsQueueMetricFactory"; 2 | export * from "./SqsQueueMonitoring"; 3 | export * from "./SqsQueueMonitoringWithDlq"; 4 | -------------------------------------------------------------------------------- /lib/monitoring/aws-step-functions/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./StepFunctionActivityMetricFactory"; 2 | export * from "./StepFunctionActivityMonitoring"; 3 | export * from "./StepFunctionLambdaIntegrationMetricFactory"; 4 | export * from "./StepFunctionLambdaIntegrationMonitoring"; 5 | export * from "./StepFunctionMetricFactory"; 6 | export * from "./StepFunctionMonitoring"; 7 | export * from "./StepFunctionServiceIntegrationMetricFactory"; 8 | export * from "./StepFunctionServiceIntegrationMonitoring"; 9 | -------------------------------------------------------------------------------- /lib/monitoring/aws-synthetics/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./SyntheticsCanaryMetricFactory"; 2 | export * from "./SyntheticsCanaryMonitoring"; 3 | -------------------------------------------------------------------------------- /lib/monitoring/aws-wafv2/WafV2MetricFactory.ts: -------------------------------------------------------------------------------- 1 | import { DimensionHash } from "aws-cdk-lib/aws-cloudwatch"; 2 | import { CfnWebACL } from "aws-cdk-lib/aws-wafv2"; 3 | import { 4 | BaseMetricFactory, 5 | BaseMetricFactoryProps, 6 | MetricFactory, 7 | MetricStatistic, 8 | } from "../../common"; 9 | 10 | const MetricNamespace = "AWS/WAFV2"; 11 | const AllRulesDimensionValue = "ALL"; 12 | 13 | export interface WafV2MetricFactoryProps extends BaseMetricFactoryProps { 14 | /** 15 | * Note that the "region" prop is required if this has a "REGIONAL" scope. 16 | */ 17 | readonly acl: CfnWebACL; 18 | } 19 | 20 | /** 21 | * https://docs.aws.amazon.com/waf/latest/developerguide/monitoring-cloudwatch.html 22 | */ 23 | export class WafV2MetricFactory extends BaseMetricFactory { 24 | protected readonly dimensions: DimensionHash; 25 | 26 | constructor(metricFactory: MetricFactory, props: WafV2MetricFactoryProps) { 27 | super(metricFactory, props); 28 | 29 | if (props.acl.scope === "REGIONAL" && !props.region) { 30 | throw new Error(`region is required if CfnWebACL has "REGIONAL" scope`); 31 | } 32 | 33 | this.dimensions = { 34 | Rule: AllRulesDimensionValue, 35 | WebACL: props.acl.name, 36 | ...(props.region && { Region: props.region }), 37 | }; 38 | } 39 | 40 | metricAllowedRequests() { 41 | return this.metricFactory.createMetric( 42 | "AllowedRequests", 43 | MetricStatistic.SUM, 44 | "Allowed", 45 | this.dimensions, 46 | undefined, 47 | MetricNamespace, 48 | undefined, 49 | this.region, 50 | this.account, 51 | ); 52 | } 53 | 54 | metricBlockedRequests() { 55 | return this.metricFactory.createMetric( 56 | "BlockedRequests", 57 | MetricStatistic.SUM, 58 | "Blocked", 59 | this.dimensions, 60 | undefined, 61 | MetricNamespace, 62 | undefined, 63 | this.region, 64 | this.account, 65 | ); 66 | } 67 | 68 | metricBlockedRequestsRate() { 69 | return this.metricFactory.createMetricMath( 70 | "100 * (blocked / (allowed + blocked))", 71 | { 72 | allowed: this.metricAllowedRequests(), 73 | blocked: this.metricBlockedRequests(), 74 | }, 75 | "Blocked (rate)", 76 | undefined, 77 | undefined, 78 | this.region, 79 | this.account, 80 | ); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /lib/monitoring/aws-wafv2/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./WafV2MetricFactory"; 2 | export * from "./WafV2Monitoring"; 3 | -------------------------------------------------------------------------------- /lib/monitoring/custom/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./CustomMonitoring"; 2 | -------------------------------------------------------------------------------- /lib/monitoring/fluentbit/FluentBitConstants.ts: -------------------------------------------------------------------------------- 1 | export enum FluentBitStorageMetricTag { 2 | TOTAL_CHUNKS = "total_chunks", 3 | MEM_CHUNKS = "mem_chunks", 4 | FS_CHUNKS = "fs_chunks", 5 | FS_CHUNKS_UP = "fs_chunks_up", 6 | FS_CHUNKS_DOWN = "fs_chunks_down", 7 | } 8 | 9 | export enum FluentBitOutputMetricTag { 10 | OUTPUT_RETRIES = "fluentbit_output_retries_total", 11 | OUTPUT_RETRIES_FAILED = "fluentbit_output_retries_failed_total", 12 | OUTPUT_ERRORS = "fluentbit_output_errors_total", 13 | OUTPUT_DROPPED_RECORDS = "fluentbit_output_dropped_records_total", 14 | } 15 | 16 | export enum FluentBitInputMetricTag { 17 | INPUT_RECORDS = "fluentbit_input_records_total", 18 | } 19 | export enum FluentBitFilterMetricTag { 20 | FILTER_EMIT_RECORDS = "fluentbit_filter_emit_records_total", 21 | FILTER_DROP_RECORDS = "fluentbit_filter_drop_records_total", 22 | FILTER_ADD_RECORDS = "fluentbit_filter_add_records_total", 23 | } 24 | 25 | export enum FluentBitMetricsWithoutWidget { 26 | INPUT_BYTES = "fluentbit_input_bytes_total", 27 | OUTPUT_PROC_RECORDS = "fluentbit_output_proc_records_total", 28 | OUTPUT_PROC_BYTES = "fluentbit_output_proc_bytes_total", 29 | } 30 | -------------------------------------------------------------------------------- /lib/monitoring/fluentbit/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./FluentBitConstants"; 2 | export * from "./FluentBitMetricFactory"; 3 | export * from "./FluentBitMonitoring"; 4 | -------------------------------------------------------------------------------- /lib/monitoring/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./aws-acm"; 2 | export * from "./aws-apigateway"; 3 | export * from "./aws-apigatewayv2"; 4 | export * from "./aws-appsync"; 5 | export * from "./aws-billing"; 6 | export * from "./aws-cloudfront"; 7 | export * from "./aws-cloudwatch"; 8 | export * from "./aws-codebuild"; 9 | export * from "./aws-docdb"; 10 | export * from "./aws-dynamo"; 11 | export * from "./aws-ec2"; 12 | export * from "./aws-ecs-patterns"; 13 | export * from "./aws-elasticache"; 14 | export * from "./aws-glue"; 15 | export * from "./aws-kinesis"; 16 | export * from "./aws-kinesisanalytics"; 17 | export * from "./aws-lambda"; 18 | export * from "./aws-loadbalancing"; 19 | export * from "./aws-opensearch"; 20 | export * from "./aws-rds"; 21 | export * from "./aws-redshift"; 22 | export * from "./aws-s3"; 23 | export * from "./aws-secretsmanager"; 24 | export * from "./aws-sns"; 25 | export * from "./aws-sqs"; 26 | export * from "./aws-step-functions"; 27 | export * from "./aws-synthetics"; 28 | export * from "./aws-wafv2"; 29 | export * from "./custom"; 30 | export * from "./fluentbit"; 31 | -------------------------------------------------------------------------------- /test/assets/schema.graphql: -------------------------------------------------------------------------------- 1 | type demo { 2 | id: String! 3 | version: String! 4 | } 5 | type Query { 6 | getDemos: [ demo! ] 7 | } 8 | input DemoInput { 9 | version: String! 10 | } 11 | type Mutation { 12 | addDemo(input: DemoInput!): demo 13 | } 14 | -------------------------------------------------------------------------------- /test/common/alarm/LatencyAlarmFactory.test.ts: -------------------------------------------------------------------------------- 1 | import { Duration, Stack } from "aws-cdk-lib"; 2 | import { Metric } from "aws-cdk-lib/aws-cloudwatch"; 3 | import { Construct } from "constructs"; 4 | 5 | import { 6 | AlarmFactory, 7 | AlarmFactoryDefaults, 8 | LatencyAlarmFactory, 9 | LatencyType, 10 | MetricFactoryDefaults, 11 | noopAction, 12 | } from "../../../lib"; 13 | 14 | const stack = new Stack(); 15 | const construct = new Construct(stack, "SampleConstruct"); 16 | 17 | const globalMetricDefaults: MetricFactoryDefaults = { 18 | namespace: "DummyNamespace", 19 | }; 20 | const globalAlarmDefaults: AlarmFactoryDefaults = { 21 | alarmNamePrefix: "DummyServiceAlarms", 22 | actionsEnabled: true, 23 | datapointsToAlarm: 6, 24 | // we do not care about alarm actions in this test 25 | action: noopAction(), 26 | }; 27 | const factory = new AlarmFactory(construct, { 28 | globalMetricDefaults, 29 | globalAlarmDefaults, 30 | localAlarmNamePrefix: "prefix", 31 | }); 32 | 33 | const metric = new Metric({ 34 | metricName: "DummyMetric1", 35 | namespace: "DummyCustomNamespace", 36 | dimensionsMap: { CustomDimension: "CustomDimensionValue" }, 37 | }); 38 | 39 | const latencyAlarmFactory = new LatencyAlarmFactory(factory); 40 | 41 | test("addLatencyAlarm: non-integral millisecond thresholds do not throw error", () => { 42 | latencyAlarmFactory.addLatencyAlarm(metric, LatencyType.P99, { 43 | maxLatency: Duration.millis(0.5), 44 | }); 45 | }); 46 | 47 | test("addIntegrationLatencyAlarm: non-integral millisecond thresholds do not throw error", () => { 48 | latencyAlarmFactory.addIntegrationLatencyAlarm(metric, LatencyType.P99, { 49 | maxLatency: Duration.millis(2.5), 50 | }); 51 | }); 52 | 53 | test("addDurationAlarm: non-integral millisecond durations do not throw error", () => { 54 | latencyAlarmFactory.addDurationAlarm(metric, LatencyType.P99, { 55 | maxDuration: Duration.millis(0.5), 56 | }); 57 | }); 58 | 59 | test("addJvmGarbageCollectionDurationAlarm: non-integral millisecond durations do not throw error", () => { 60 | latencyAlarmFactory.addJvmGarbageCollectionDurationAlarm( 61 | metric, 62 | LatencyType.P99, 63 | { 64 | maxDuration: Duration.millis(2.5), 65 | }, 66 | ); 67 | }); 68 | -------------------------------------------------------------------------------- /test/common/alarm/LatencyType.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | LatencyType, 3 | getLatencyTypeExpressionId, 4 | getLatencyTypeLabel, 5 | getLatencyTypeStatistic, 6 | } from "../../../lib"; 7 | 8 | test("latency type getters work for all enum values", () => { 9 | Object.values(LatencyType).forEach((latencyType) => { 10 | expect(getLatencyTypeStatistic(latencyType)).toBeTruthy(); 11 | expect(getLatencyTypeExpressionId(latencyType)).toBeTruthy(); 12 | expect(getLatencyTypeLabel(latencyType)).toBeTruthy(); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /test/common/alarm/action/LambdaAlarmActionStrategy.test.ts: -------------------------------------------------------------------------------- 1 | import { Stack } from "aws-cdk-lib"; 2 | import { Template } from "aws-cdk-lib/assertions"; 3 | import { Alarm, Metric } from "aws-cdk-lib/aws-cloudwatch"; 4 | import { 5 | determineLatestNodeRuntime, 6 | Function, 7 | InlineCode, 8 | } from "aws-cdk-lib/aws-lambda"; 9 | 10 | import { LambdaAlarmActionStrategy } from "../../../../lib"; 11 | 12 | test("snapshot test: Lambda function", () => { 13 | const stack = new Stack(); 14 | const onAlarmFunction = new Function(stack, "alarmLambda", { 15 | functionName: "DummyLambda", 16 | runtime: determineLatestNodeRuntime(stack), 17 | code: InlineCode.fromInline("{}"), 18 | handler: "Dummy::handler", 19 | }); 20 | const alarm = new Alarm(stack, "DummyAlarm", { 21 | evaluationPeriods: 1, 22 | threshold: 0, 23 | metric: new Metric({ namespace: "Dummy", metricName: "Dummy" }), 24 | }); 25 | const action = new LambdaAlarmActionStrategy(onAlarmFunction); 26 | action.addAlarmActions({ alarm, action }); 27 | 28 | expect(Template.fromStack(stack)).toMatchSnapshot(); 29 | }); 30 | 31 | test("snapshot test: Lambda alias", () => { 32 | const stack = new Stack(); 33 | const onAlarmFunction = new Function(stack, "alarmLambda", { 34 | functionName: "DummyLambda", 35 | runtime: determineLatestNodeRuntime(stack), 36 | code: InlineCode.fromInline("{}"), 37 | handler: "Dummy::handler", 38 | }); 39 | const alias = onAlarmFunction.addAlias("aliasName"); 40 | const alarm = new Alarm(stack, "DummyAlarm", { 41 | evaluationPeriods: 1, 42 | threshold: 0, 43 | metric: new Metric({ namespace: "Dummy", metricName: "Dummy" }), 44 | }); 45 | const action = new LambdaAlarmActionStrategy(alias); 46 | action.addAlarmActions({ alarm, action }); 47 | 48 | expect(Template.fromStack(stack)).toMatchSnapshot(); 49 | }); 50 | 51 | test("snapshot test: Lambda version", () => { 52 | const stack = new Stack(); 53 | const onAlarmFunction = new Function(stack, "alarmLambda", { 54 | functionName: "DummyLambda", 55 | runtime: determineLatestNodeRuntime(stack), 56 | code: InlineCode.fromInline("{}"), 57 | handler: "Dummy::handler", 58 | }); 59 | const version = onAlarmFunction.currentVersion; 60 | const alarm = new Alarm(stack, "DummyAlarm", { 61 | evaluationPeriods: 1, 62 | threshold: 0, 63 | metric: new Metric({ namespace: "Dummy", metricName: "Dummy" }), 64 | }); 65 | const action = new LambdaAlarmActionStrategy(version); 66 | action.addAlarmActions({ alarm, action }); 67 | 68 | expect(Template.fromStack(stack)).toMatchSnapshot(); 69 | }); 70 | -------------------------------------------------------------------------------- /test/common/alarm/action/MultipleAlarmActionStrategy.test.ts: -------------------------------------------------------------------------------- 1 | import { Stack } from "aws-cdk-lib"; 2 | import { Template } from "aws-cdk-lib/assertions"; 3 | import { Alarm, Metric } from "aws-cdk-lib/aws-cloudwatch"; 4 | import { Topic } from "aws-cdk-lib/aws-sns"; 5 | 6 | import { 7 | isMultipleAlarmActionStrategy, 8 | multipleActions, 9 | SnsAlarmActionStrategy, 10 | } from "../../../../lib"; 11 | 12 | test("snapshot test: multiple actions", () => { 13 | const stack = new Stack(); 14 | const topic1 = new Topic(stack, "DummyTopic1"); 15 | const topic2 = new Topic(stack, "DummyTopic2"); 16 | const topic3 = new Topic(stack, "DummyTopic3"); 17 | const alarm = new Alarm(stack, "DummyAlarm", { 18 | evaluationPeriods: 1, 19 | threshold: 0, 20 | metric: new Metric({ namespace: "Dummy", metricName: "Dummy" }), 21 | }); 22 | 23 | const action1 = new SnsAlarmActionStrategy({ onAlarmTopic: topic1 }); 24 | const action2 = new SnsAlarmActionStrategy({ onAlarmTopic: topic2 }); 25 | const action3 = new SnsAlarmActionStrategy({ onAlarmTopic: topic3 }); 26 | 27 | const action = multipleActions(action1, action2, action3); 28 | action.addAlarmActions({ alarm, action }); 29 | 30 | expect(Template.fromStack(stack)).toMatchSnapshot(); 31 | expect(action.flattenedAlarmActions()).toEqual([action1, action2, action3]); 32 | }); 33 | 34 | test("isMultipleAlarmActionStrategy", () => { 35 | const stack = new Stack(); 36 | const topic1 = new Topic(stack, "DummyTopic1"); 37 | const snsAction = new SnsAlarmActionStrategy({ onAlarmTopic: topic1 }); 38 | const multipleAction = multipleActions(snsAction); 39 | 40 | expect(isMultipleAlarmActionStrategy(multipleAction)).toBe(true); 41 | expect(isMultipleAlarmActionStrategy(snsAction)).toBe(false); 42 | }); 43 | -------------------------------------------------------------------------------- /test/common/alarm/action/OpsItemAlarmActionStrategy.test.ts: -------------------------------------------------------------------------------- 1 | import { Stack } from "aws-cdk-lib"; 2 | import { Template } from "aws-cdk-lib/assertions"; 3 | import { Alarm, Metric } from "aws-cdk-lib/aws-cloudwatch"; 4 | import { 5 | OpsItemCategory, 6 | OpsItemSeverity, 7 | } from "aws-cdk-lib/aws-cloudwatch-actions"; 8 | 9 | import { OpsItemAlarmActionStrategy } from "../../../../lib"; 10 | 11 | test("snapshot test", () => { 12 | const stack = new Stack(); 13 | const alarm = new Alarm(stack, "DummyAlarm", { 14 | evaluationPeriods: 1, 15 | threshold: 0, 16 | metric: new Metric({ namespace: "Dummy", metricName: "Dummy" }), 17 | }); 18 | const action = new OpsItemAlarmActionStrategy( 19 | OpsItemSeverity.LOW, 20 | OpsItemCategory.AVAILABILITY, 21 | ); 22 | action.addAlarmActions({ alarm, action }); 23 | 24 | expect(Template.fromStack(stack)).toMatchSnapshot(); 25 | }); 26 | -------------------------------------------------------------------------------- /test/common/alarm/action/SnsAlarmActionStrategy.test.ts: -------------------------------------------------------------------------------- 1 | import { Stack } from "aws-cdk-lib"; 2 | import { Template } from "aws-cdk-lib/assertions"; 3 | import { Alarm, Metric } from "aws-cdk-lib/aws-cloudwatch"; 4 | import { Topic } from "aws-cdk-lib/aws-sns"; 5 | 6 | import { SnsAlarmActionStrategy } from "../../../../lib"; 7 | 8 | test("snapshot test: default action only", () => { 9 | const stack = new Stack(); 10 | const onAlarmTopic = new Topic(stack, "DummyTopic"); 11 | const alarm = new Alarm(stack, "DummyAlarm", { 12 | evaluationPeriods: 1, 13 | threshold: 0, 14 | metric: new Metric({ namespace: "Dummy", metricName: "Dummy" }), 15 | }); 16 | const action = new SnsAlarmActionStrategy({ onAlarmTopic }); 17 | action.addAlarmActions({ alarm, action }); 18 | 19 | expect(Template.fromStack(stack)).toMatchSnapshot(); 20 | }); 21 | 22 | test("snapshot test: all actions", () => { 23 | const stack = new Stack(); 24 | const onAlarmTopic = new Topic(stack, "DummyTopic"); 25 | const onOkTopic = new Topic(stack, "DummyOkTopic"); 26 | const onInsufficientDataTopic = new Topic( 27 | stack, 28 | "DummyInsufficientDataTopic", 29 | ); 30 | const alarm = new Alarm(stack, "DummyAlarm", { 31 | evaluationPeriods: 1, 32 | threshold: 0, 33 | metric: new Metric({ namespace: "Dummy", metricName: "Dummy" }), 34 | }); 35 | const action = new SnsAlarmActionStrategy({ 36 | onAlarmTopic, 37 | onOkTopic, 38 | onInsufficientDataTopic, 39 | }); 40 | action.addAlarmActions({ alarm, action }); 41 | 42 | expect(Template.fromStack(stack)).toMatchSnapshot(); 43 | }); 44 | -------------------------------------------------------------------------------- /test/common/alarm/action/__snapshots__/MultipleAlarmActionStrategy.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`snapshot test: multiple actions 1`] = ` 4 | Object { 5 | "Parameters": Object { 6 | "BootstrapVersion": Object { 7 | "Default": "/cdk-bootstrap/hnb659fds/version", 8 | "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]", 9 | "Type": "AWS::SSM::Parameter::Value", 10 | }, 11 | }, 12 | "Resources": Object { 13 | "DummyAlarm234203A9": Object { 14 | "Properties": Object { 15 | "AlarmActions": Array [ 16 | Object { 17 | "Ref": "DummyTopic18541D2D0", 18 | }, 19 | Object { 20 | "Ref": "DummyTopic2F00C887A", 21 | }, 22 | Object { 23 | "Ref": "DummyTopic389AA825B", 24 | }, 25 | ], 26 | "ComparisonOperator": "GreaterThanOrEqualToThreshold", 27 | "EvaluationPeriods": 1, 28 | "MetricName": "Dummy", 29 | "Namespace": "Dummy", 30 | "Period": 300, 31 | "Statistic": "Average", 32 | "Threshold": 0, 33 | }, 34 | "Type": "AWS::CloudWatch::Alarm", 35 | }, 36 | "DummyTopic18541D2D0": Object { 37 | "Type": "AWS::SNS::Topic", 38 | }, 39 | "DummyTopic2F00C887A": Object { 40 | "Type": "AWS::SNS::Topic", 41 | }, 42 | "DummyTopic389AA825B": Object { 43 | "Type": "AWS::SNS::Topic", 44 | }, 45 | }, 46 | "Rules": Object { 47 | "CheckBootstrapVersion": Object { 48 | "Assertions": Array [ 49 | Object { 50 | "Assert": Object { 51 | "Fn::Not": Array [ 52 | Object { 53 | "Fn::Contains": Array [ 54 | Array [ 55 | "1", 56 | "2", 57 | "3", 58 | "4", 59 | "5", 60 | ], 61 | Object { 62 | "Ref": "BootstrapVersion", 63 | }, 64 | ], 65 | }, 66 | ], 67 | }, 68 | "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI.", 69 | }, 70 | ], 71 | }, 72 | }, 73 | } 74 | `; 75 | -------------------------------------------------------------------------------- /test/common/alarm/action/__snapshots__/OpsItemAlarmActionStrategy.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`snapshot test 1`] = ` 4 | Object { 5 | "Parameters": Object { 6 | "BootstrapVersion": Object { 7 | "Default": "/cdk-bootstrap/hnb659fds/version", 8 | "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]", 9 | "Type": "AWS::SSM::Parameter::Value", 10 | }, 11 | }, 12 | "Resources": Object { 13 | "DummyAlarm234203A9": Object { 14 | "Properties": Object { 15 | "AlarmActions": Array [ 16 | Object { 17 | "Fn::Join": Array [ 18 | "", 19 | Array [ 20 | "arn:", 21 | Object { 22 | "Ref": "AWS::Partition", 23 | }, 24 | ":ssm:", 25 | Object { 26 | "Ref": "AWS::Region", 27 | }, 28 | ":", 29 | Object { 30 | "Ref": "AWS::AccountId", 31 | }, 32 | ":opsitem:4#CATEGORY=Availability", 33 | ], 34 | ], 35 | }, 36 | ], 37 | "ComparisonOperator": "GreaterThanOrEqualToThreshold", 38 | "EvaluationPeriods": 1, 39 | "MetricName": "Dummy", 40 | "Namespace": "Dummy", 41 | "Period": 300, 42 | "Statistic": "Average", 43 | "Threshold": 0, 44 | }, 45 | "Type": "AWS::CloudWatch::Alarm", 46 | }, 47 | }, 48 | "Rules": Object { 49 | "CheckBootstrapVersion": Object { 50 | "Assertions": Array [ 51 | Object { 52 | "Assert": Object { 53 | "Fn::Not": Array [ 54 | Object { 55 | "Fn::Contains": Array [ 56 | Array [ 57 | "1", 58 | "2", 59 | "3", 60 | "4", 61 | "5", 62 | ], 63 | Object { 64 | "Ref": "BootstrapVersion", 65 | }, 66 | ], 67 | }, 68 | ], 69 | }, 70 | "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI.", 71 | }, 72 | ], 73 | }, 74 | }, 75 | } 76 | `; 77 | -------------------------------------------------------------------------------- /test/common/alarm/metric-adjuster/CompositeMetricAdjuster.test.ts: -------------------------------------------------------------------------------- 1 | import { Stack } from "aws-cdk-lib"; 2 | import { 3 | ComparisonOperator, 4 | Metric, 5 | TreatMissingData, 6 | } from "aws-cdk-lib/aws-cloudwatch"; 7 | import { AddAlarmProps, CompositeMetricAdjuster } from "../../../../lib"; 8 | 9 | const metric = new Metric({ 10 | namespace: "MyNamespace", 11 | metricName: "MyMetric", 12 | label: "Hello", 13 | }); 14 | 15 | const alarmScope = new Stack(); 16 | 17 | const props: AddAlarmProps = { 18 | alarmDescription: "", 19 | alarmNameSuffix: " World", 20 | threshold: 0, 21 | comparisonOperator: ComparisonOperator.GREATER_THAN_THRESHOLD, 22 | treatMissingData: TreatMissingData.MISSING, 23 | }; 24 | 25 | test("adjustMetric: no metric adjustments, returns untouched metric", () => { 26 | const metricAdjuster = CompositeMetricAdjuster.of(); 27 | 28 | const actual = metricAdjuster.adjustMetric(metric, alarmScope, props); 29 | 30 | expect(actual).toBe(metric); 31 | }); 32 | 33 | test("adjustMetric: with metric adjustments, applies metric adjustments in specified order", () => { 34 | const metricAdjuster = CompositeMetricAdjuster.of( 35 | { 36 | adjustMetric(metric, _, props) { 37 | return metric.with({ label: metric.label + props.alarmNameSuffix }); 38 | }, 39 | }, 40 | { 41 | adjustMetric(metric) { 42 | return metric.with({ label: metric.label + "!" }); 43 | }, 44 | }, 45 | ); 46 | 47 | const actual = metricAdjuster.adjustMetric(metric, alarmScope, props); 48 | 49 | expect(actual).not.toBe(metric); 50 | expect(actual.label).toEqual("Hello World!"); 51 | }); 52 | -------------------------------------------------------------------------------- /test/common/alarm/metric-adjuster/DefaultMetricAdjuster.test.ts: -------------------------------------------------------------------------------- 1 | import { Duration, Stack } from "aws-cdk-lib"; 2 | import { 3 | ComparisonOperator, 4 | Metric, 5 | TreatMissingData, 6 | } from "aws-cdk-lib/aws-cloudwatch"; 7 | import { DefaultMetricAdjuster } from "../../../../lib"; 8 | 9 | test("adjustMetric: applies default adjustments", () => { 10 | const actual = DefaultMetricAdjuster.INSTANCE.adjustMetric( 11 | new Metric({ 12 | namespace: "MyNamespace", 13 | metricName: "MyMetric", 14 | label: "My (label) with (some ${text}) ${content}.", 15 | period: Duration.minutes(1), 16 | }), 17 | new Stack(), 18 | { 19 | alarmDescription: "", 20 | alarmNameSuffix: "", 21 | threshold: 0, 22 | comparisonOperator: ComparisonOperator.GREATER_THAN_THRESHOLD, 23 | treatMissingData: TreatMissingData.MISSING, 24 | period: Duration.minutes(5), 25 | }, 26 | ); 27 | 28 | expect(actual.period).toEqual(Duration.minutes(5)); 29 | expect(actual.label).toBe("My (label) with ${content}."); 30 | }); 31 | -------------------------------------------------------------------------------- /test/common/strings.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | capitalizeFirstLetter, 3 | capitalizeFirstLetterOnly, 4 | getHashForMetricExpressionId, 5 | removeBracketsWithDynamicLabels, 6 | } from "../../lib"; 7 | 8 | test("capitalize first letter", () => { 9 | expect(capitalizeFirstLetter("")).toStrictEqual(""); 10 | expect(capitalizeFirstLetter("a")).toStrictEqual("A"); 11 | expect(capitalizeFirstLetter("aa")).toStrictEqual("Aa"); 12 | expect(capitalizeFirstLetter("Aa")).toStrictEqual("Aa"); 13 | expect(capitalizeFirstLetter("AA")).toStrictEqual("AA"); 14 | }); 15 | 16 | test("capitalize first letter only", () => { 17 | expect(capitalizeFirstLetterOnly("")).toStrictEqual(""); 18 | expect(capitalizeFirstLetterOnly("a")).toStrictEqual("A"); 19 | expect(capitalizeFirstLetterOnly("aa")).toStrictEqual("Aa"); 20 | expect(capitalizeFirstLetterOnly("Aa")).toStrictEqual("Aa"); 21 | expect(capitalizeFirstLetterOnly("AA")).toStrictEqual("Aa"); 22 | }); 23 | 24 | test("removeBracketsWithDynamicLabels: empty string", () => { 25 | expect(removeBracketsWithDynamicLabels("")).toEqual(""); 26 | }); 27 | 28 | test("removeBracketsWithDynamicLabels: plain string", () => { 29 | expect(removeBracketsWithDynamicLabels("abc")).toEqual("abc"); 30 | }); 31 | 32 | test("removeBracketsWithDynamicLabels: plain string with brackets (no placeholders)", () => { 33 | expect(removeBracketsWithDynamicLabels("a (b) c")).toEqual("a (b) c"); 34 | }); 35 | 36 | test("removeBracketsWithDynamicLabels: string with single removal", () => { 37 | expect(removeBracketsWithDynamicLabels("a (b: ${c}) d")).toEqual("a d"); 38 | }); 39 | 40 | test("removeBracketsWithDynamicLabels: string with multiple removals", () => { 41 | expect(removeBracketsWithDynamicLabels("a (b: ${c}) (d: ${e}) f")).toEqual( 42 | "a f", 43 | ); 44 | }); 45 | 46 | test("getMetricExpressionIdUsingHash: empty string", () => { 47 | expect(getHashForMetricExpressionId("")).toStrictEqual("f56f10af2d6c9"); 48 | }); 49 | 50 | test("getMetricExpressionIdUsingHash: plain string", () => { 51 | expect(getHashForMetricExpressionId("Hello world!")).toStrictEqual( 52 | "ff4e5fadf3425", 53 | ); 54 | }); 55 | -------------------------------------------------------------------------------- /test/common/widget/size.test.ts: -------------------------------------------------------------------------------- 1 | import { 2 | FullWidth, 3 | HalfQuarterWidth, 4 | HalfWidth, 5 | QuarterWidth, 6 | SixthWidth, 7 | ThirdWidth, 8 | ThreeQuartersWidth, 9 | TwoThirdsWidth, 10 | recommendedWidgetWidth, 11 | } from "../../../lib"; 12 | 13 | test("widget width recommendation", () => { 14 | // edge case 15 | expect(recommendedWidgetWidth(-1)).toStrictEqual(FullWidth); 16 | expect(recommendedWidgetWidth(0)).toStrictEqual(FullWidth); 17 | // typical cases 18 | expect(recommendedWidgetWidth(1)).toStrictEqual(FullWidth); 19 | expect(recommendedWidgetWidth(2)).toStrictEqual(HalfWidth); 20 | expect(recommendedWidgetWidth(3)).toStrictEqual(ThirdWidth); 21 | expect(recommendedWidgetWidth(4)).toStrictEqual(QuarterWidth); 22 | expect(recommendedWidgetWidth(5)).toStrictEqual(ThirdWidth); 23 | expect(recommendedWidgetWidth(6)).toStrictEqual(ThirdWidth); 24 | expect(recommendedWidgetWidth(7)).toStrictEqual(QuarterWidth); 25 | expect(recommendedWidgetWidth(8)).toStrictEqual(QuarterWidth); 26 | expect(recommendedWidgetWidth(9)).toStrictEqual(ThirdWidth); 27 | expect(recommendedWidgetWidth(10)).toStrictEqual(QuarterWidth); 28 | expect(recommendedWidgetWidth(11)).toStrictEqual(QuarterWidth); 29 | expect(recommendedWidgetWidth(12)).toStrictEqual(QuarterWidth); 30 | // extreme cases 31 | expect(recommendedWidgetWidth(100)).toStrictEqual(QuarterWidth); 32 | expect(recommendedWidgetWidth(1000)).toStrictEqual(QuarterWidth); 33 | expect(recommendedWidgetWidth(1001)).toStrictEqual(QuarterWidth); 34 | expect(recommendedWidgetWidth(1002)).toStrictEqual(QuarterWidth); 35 | expect(recommendedWidgetWidth(1003)).toStrictEqual(QuarterWidth); 36 | expect(recommendedWidgetWidth(1004)).toStrictEqual(QuarterWidth); 37 | }); 38 | 39 | test("widget widths add up as their name promises", () => { 40 | expect(6 * SixthWidth).toStrictEqual(FullWidth); 41 | expect(4 * QuarterWidth).toStrictEqual(FullWidth); 42 | expect(3 * ThirdWidth).toStrictEqual(FullWidth); 43 | expect(2 * HalfWidth).toStrictEqual(FullWidth); 44 | expect(ThirdWidth + TwoThirdsWidth).toStrictEqual(FullWidth); 45 | expect(QuarterWidth + ThreeQuartersWidth).toStrictEqual(FullWidth); 46 | expect(2 * HalfQuarterWidth).toStrictEqual(QuarterWidth); 47 | }); 48 | -------------------------------------------------------------------------------- /test/dashboard/BitmapDashboard.test.ts: -------------------------------------------------------------------------------- 1 | import { Stack } from "aws-cdk-lib"; 2 | import { Template } from "aws-cdk-lib/assertions"; 3 | import { Column, GraphWidget, Metric, Row } from "aws-cdk-lib/aws-cloudwatch"; 4 | 5 | import { BitmapDashboard } from "../../lib"; 6 | 7 | test("check the bitmap dashboard is generated with rows/columns", () => { 8 | const stack = new Stack(); 9 | 10 | const dashboard = new BitmapDashboard(stack, "UnitUnderTest", {}); 11 | 12 | dashboard.addWidgets( 13 | new Row( 14 | new Column( 15 | new GraphWidget({ 16 | title: "A1", 17 | left: [ 18 | new Metric({ metricName: "DummyMetric1", namespace: "Dummy" }), 19 | ], 20 | }), 21 | ), 22 | new Column( 23 | new GraphWidget({ 24 | title: "A2", 25 | left: [ 26 | new Metric({ metricName: "DummyMetric2", namespace: "Dummy" }), 27 | ], 28 | }), 29 | ), 30 | ), 31 | new Row( 32 | new Column( 33 | new GraphWidget({ 34 | title: "B1", 35 | left: [ 36 | new Metric({ metricName: "DummyMetric3", namespace: "Dummy" }), 37 | ], 38 | }), 39 | ), 40 | new Column( 41 | new GraphWidget({ 42 | title: "B2", 43 | left: [ 44 | new Metric({ metricName: "DummyMetric4", namespace: "Dummy" }), 45 | ], 46 | }), 47 | ), 48 | ), 49 | ); 50 | 51 | Template.fromStack(stack).resourceCountIs("AWS::CloudWatch::Dashboard", 1); 52 | }); 53 | -------------------------------------------------------------------------------- /test/dashboard/DashboardWithBitmapCopy.test.ts: -------------------------------------------------------------------------------- 1 | import { Stack } from "aws-cdk-lib"; 2 | import { Template } from "aws-cdk-lib/assertions"; 3 | 4 | import { DashboardWithBitmapCopy } from "../../lib"; 5 | 6 | test("named dashboard has a bitmap copy", () => { 7 | const stack = new Stack(); 8 | 9 | new DashboardWithBitmapCopy(stack, "UnitUnderTest", { 10 | dashboardName: "DummyDashboard", 11 | }); 12 | 13 | const template = Template.fromStack(stack); 14 | template.hasResourceProperties("AWS::CloudWatch::Dashboard", { 15 | DashboardName: "DummyDashboard", 16 | }); 17 | template.hasResourceProperties("AWS::CloudWatch::Dashboard", { 18 | DashboardName: "Bitmap-DummyDashboard", 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /test/dashboard/widget/AlarmMatrixWidget.test.ts: -------------------------------------------------------------------------------- 1 | import { Stack } from "aws-cdk-lib"; 2 | import { Alarm, Metric } from "aws-cdk-lib/aws-cloudwatch"; 3 | 4 | import { AlarmMatrixWidget } from "../../../lib/dashboard/widget/AlarmMatrixWidget"; 5 | 6 | test("no alarms", () => { 7 | const widget = new AlarmMatrixWidget({ title: "Title", alarms: [] }); 8 | 9 | expect(widget.toJson()).toEqual([ 10 | { 11 | height: 2, 12 | properties: { 13 | alarms: [], 14 | title: "Title", 15 | }, 16 | type: "alarm", 17 | width: 24, 18 | x: undefined, 19 | y: undefined, 20 | }, 21 | ]); 22 | }); 23 | 24 | test("small number of alarms", () => { 25 | const stack = new Stack(); 26 | 27 | const alarm = new Alarm(stack, "Alarm", { 28 | metric: new Metric({ metricName: "Metric", namespace: "Namespace" }), 29 | threshold: 10, 30 | evaluationPeriods: 5, 31 | }); 32 | 33 | const widget = new AlarmMatrixWidget({ 34 | title: "Title", 35 | alarms: Array(6).fill(alarm), 36 | }); 37 | 38 | expect(widget.toJson()).toEqual([ 39 | { 40 | height: 2, 41 | properties: { 42 | alarms: Array(6).fill(alarm.alarmArn), 43 | title: "Title", 44 | }, 45 | type: "alarm", 46 | width: 24, 47 | x: undefined, 48 | y: undefined, 49 | }, 50 | ]); 51 | }); 52 | 53 | test("big number of alarms", () => { 54 | const stack = new Stack(); 55 | 56 | const alarm = new Alarm(stack, "Alarm", { 57 | metric: new Metric({ metricName: "Metric", namespace: "Namespace" }), 58 | threshold: 10, 59 | evaluationPeriods: 5, 60 | }); 61 | 62 | const widget = new AlarmMatrixWidget({ 63 | title: "Title", 64 | alarms: Array(17).fill(alarm), 65 | }); 66 | 67 | expect(widget.toJson()).toEqual([ 68 | { 69 | height: 3, 70 | properties: { 71 | alarms: Array(17).fill(alarm.alarmArn), 72 | title: "Title", 73 | }, 74 | type: "alarm", 75 | width: 24, 76 | x: undefined, 77 | y: undefined, 78 | }, 79 | ]); 80 | }); 81 | -------------------------------------------------------------------------------- /test/dashboard/widget/BitmapWidget.test.ts: -------------------------------------------------------------------------------- 1 | import { Stack } from "aws-cdk-lib"; 2 | import { Template } from "aws-cdk-lib/assertions"; 3 | import { GraphWidget } from "aws-cdk-lib/aws-cloudwatch"; 4 | 5 | import { BitmapWidgetRenderingSupport } from "../../../lib"; 6 | 7 | test("support", () => { 8 | const stack = new Stack(); 9 | 10 | new BitmapWidgetRenderingSupport(stack, "Support"); 11 | 12 | expect(Template.fromStack(stack)).toMatchSnapshot(); 13 | }); 14 | 15 | test("widget", () => { 16 | const stack = new Stack(); 17 | 18 | const support = new BitmapWidgetRenderingSupport(stack, "Support"); 19 | 20 | const widget = support.asBitmap( 21 | new GraphWidget({ 22 | width: 12, 23 | height: 6, 24 | title: "Dummy Title", 25 | left: [], 26 | leftAnnotations: [{ label: "Dummy Annotation", value: 10 }], 27 | }), 28 | ); 29 | 30 | expect(widget.toJson()).toEqual([ 31 | { 32 | height: 6, 33 | properties: { 34 | endpoint: support.handler.functionArn, 35 | params: { 36 | graph: { 37 | annotations: { 38 | horizontal: [ 39 | { 40 | label: "Dummy Annotation", 41 | value: 10, 42 | yAxis: "left", 43 | }, 44 | ], 45 | }, 46 | legend: undefined, 47 | liveData: undefined, 48 | metrics: undefined, 49 | period: undefined, 50 | region: stack.region, 51 | setPeriodToTimeRange: undefined, 52 | stacked: undefined, 53 | stat: undefined, 54 | title: " ", 55 | view: "timeSeries", 56 | yAxis: { 57 | left: undefined, 58 | right: undefined, 59 | }, 60 | }, 61 | }, 62 | title: "Dummy Title", 63 | updateOn: { 64 | refresh: true, 65 | resize: true, 66 | timeRange: true, 67 | }, 68 | }, 69 | type: "custom", 70 | width: 12, 71 | x: undefined, 72 | y: undefined, 73 | }, 74 | ]); 75 | }); 76 | -------------------------------------------------------------------------------- /test/dashboard/widget/CustomWidget.test.ts: -------------------------------------------------------------------------------- 1 | import { Stack } from "aws-cdk-lib"; 2 | import { 3 | Code, 4 | determineLatestNodeRuntime, 5 | Function, 6 | } from "aws-cdk-lib/aws-lambda"; 7 | 8 | import { CustomWidget } from "../../../lib/dashboard/widget/CustomWidget"; 9 | 10 | test("widget", () => { 11 | const stack = new Stack(); 12 | 13 | const handler = new Function(stack, "Function", { 14 | runtime: determineLatestNodeRuntime(stack), 15 | // code loaded from "lambda" directory 16 | code: Code.fromInline( 17 | 'exports.handler = function(event, ctx, cb) { return cb(null, "Hello World!"); }', 18 | ), 19 | // file is "dummy", function is "handler" 20 | handler: "dummy.handler", 21 | }); 22 | const handlerParams = { 23 | key: "value", 24 | }; 25 | 26 | const widget = new CustomWidget({ 27 | updateOnTimeRangeChange: false, 28 | updateOnResize: false, 29 | updateOnRefresh: true, 30 | width: 12, 31 | height: 2, 32 | title: "Testing", 33 | handler, 34 | handlerParams, 35 | }); 36 | 37 | expect(widget.toJson()).toEqual([ 38 | { 39 | height: 2, 40 | properties: { 41 | endpoint: handler.functionArn, 42 | params: { 43 | key: "value", 44 | }, 45 | title: "Testing", 46 | updateOn: { 47 | refresh: true, 48 | resize: false, 49 | timeRange: false, 50 | }, 51 | }, 52 | type: "custom", 53 | width: 12, 54 | x: undefined, 55 | y: undefined, 56 | }, 57 | ]); 58 | }); 59 | -------------------------------------------------------------------------------- /test/dashboard/widget/HeaderWidget.test.ts: -------------------------------------------------------------------------------- 1 | import { HeaderLevel, HeaderWidget } from "../../../lib/dashboard/widget"; 2 | 3 | test("snapshot test - title only", () => { 4 | const widget = new HeaderWidget("text"); 5 | 6 | expect(widget.toJson()).toMatchSnapshot(); 7 | }); 8 | 9 | test("snapshot test - title and description, no custom height", () => { 10 | const widget = new HeaderWidget("text", HeaderLevel.LARGE, "description"); 11 | 12 | expect(widget.toJson()).toMatchSnapshot(); 13 | }); 14 | 15 | test("snapshot test - title and description, with custom height", () => { 16 | const widget = new HeaderWidget( 17 | "text", 18 | HeaderLevel.LARGE, 19 | "very long description", 20 | 5, 21 | ); 22 | 23 | expect(widget.toJson()).toMatchSnapshot(); 24 | }); 25 | -------------------------------------------------------------------------------- /test/dashboard/widget/KeyValueTableWidget.test.ts: -------------------------------------------------------------------------------- 1 | import { KeyValueTableWidget } from "../../../lib/dashboard/widget"; 2 | 3 | test("snapshot test", () => { 4 | const data: [string, string][] = [ 5 | ["name", "John Wick"], 6 | ["has", "a dog"], 7 | ]; 8 | const widget = new KeyValueTableWidget(data); 9 | 10 | expect(widget.toJson()).toMatchSnapshot(); 11 | }); 12 | -------------------------------------------------------------------------------- /test/dashboard/widget/KeyValueTableWidgetV2.test.ts: -------------------------------------------------------------------------------- 1 | import { KeyValueTableWidgetV2, KeyValue } from "../../../lib/dashboard/widget"; 2 | 3 | test("snapshot test", () => { 4 | const data: KeyValue[] = [ 5 | { key: "name", value: "John Wick" }, 6 | { key: "has", value: "a dog" }, 7 | ]; 8 | const widget = new KeyValueTableWidgetV2(data); 9 | 10 | expect(widget.toJson()).toMatchSnapshot(); 11 | }); 12 | -------------------------------------------------------------------------------- /test/dashboard/widget/MonitoringHeaderWidget.test.ts: -------------------------------------------------------------------------------- 1 | import { MonitoringHeaderWidget } from "../../../lib/dashboard/widget/MonitoringHeaderWidget"; 2 | 3 | test("snapshot test: title only", () => { 4 | const widget = new MonitoringHeaderWidget({ 5 | title: "DummyTitle", 6 | }); 7 | 8 | expect(widget.toJson()).toMatchSnapshot(); 9 | }); 10 | 11 | test("snapshot test: title and family", () => { 12 | const widget = new MonitoringHeaderWidget({ 13 | title: "DummyTitle", 14 | family: "DummyFamily", 15 | }); 16 | 17 | expect(widget.toJson()).toMatchSnapshot(); 18 | }); 19 | 20 | test("snapshot test: with link", () => { 21 | const widget = new MonitoringHeaderWidget({ 22 | title: "DummyTitle", 23 | family: "DummyFamily", 24 | goToLinkUrl: "http://amazon.com", 25 | }); 26 | 27 | expect(widget.toJson()).toMatchSnapshot(); 28 | }); 29 | 30 | test("snapshot test: with link and custom text", () => { 31 | const widget = new MonitoringHeaderWidget({ 32 | title: "DummyTitle", 33 | family: "DummyFamily", 34 | goToLinkUrl: "http://amazon.com", 35 | }); 36 | 37 | expect(widget.toJson()).toMatchSnapshot(); 38 | }); 39 | 40 | test("snapshot test: with link, custom text and description", () => { 41 | const widget = new MonitoringHeaderWidget({ 42 | title: "DummyTitle", 43 | family: "DummyFamily", 44 | goToLinkUrl: "http://amazon.com", 45 | description: "CustomDescription", 46 | }); 47 | 48 | expect(widget.toJson()).toMatchSnapshot(); 49 | }); 50 | 51 | test("snapshot test: with link, custom text and description with custom height", () => { 52 | const widget = new MonitoringHeaderWidget({ 53 | title: "DummyTitle", 54 | family: "DummyFamily", 55 | goToLinkUrl: "http://amazon.com", 56 | description: "CustomDescription", 57 | descriptionHeight: 10, 58 | }); 59 | 60 | expect(widget.toJson()).toMatchSnapshot(); 61 | }); 62 | -------------------------------------------------------------------------------- /test/dashboard/widget/UnofficialWidgets.test.ts: -------------------------------------------------------------------------------- 1 | import { AlarmSummaryMatrixWidget } from "../../../lib/dashboard/widget/UnofficialWidgets"; 2 | 3 | test("AlarmSummaryMatrixWidget: snapshot test", () => { 4 | const widget = new AlarmSummaryMatrixWidget({ 5 | alarmArns: [ 6 | "arn:aws:cloudwatch:us-west-2:123456789012:alarm:TestAlarm1", 7 | "arn:aws:cloudwatch:us-west-2:123456789012:alarm:TestAlarm2", 8 | "arn:aws:cloudwatch:us-west-2:123456789012:alarm:TestAlarm3", 9 | ], 10 | }); 11 | 12 | expect(widget.toJson()).toMatchSnapshot(); 13 | }); 14 | -------------------------------------------------------------------------------- /test/dashboard/widget/__snapshots__/HeaderWidget.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`snapshot test - title and description, no custom height 1`] = ` 4 | Array [ 5 | Object { 6 | "height": 2, 7 | "properties": Object { 8 | "background": undefined, 9 | "markdown": "# text 10 | 11 | description", 12 | }, 13 | "type": "text", 14 | "width": 24, 15 | "x": undefined, 16 | "y": undefined, 17 | }, 18 | ] 19 | `; 20 | 21 | exports[`snapshot test - title and description, with custom height 1`] = ` 22 | Array [ 23 | Object { 24 | "height": 6, 25 | "properties": Object { 26 | "background": undefined, 27 | "markdown": "# text 28 | 29 | very long description", 30 | }, 31 | "type": "text", 32 | "width": 24, 33 | "x": undefined, 34 | "y": undefined, 35 | }, 36 | ] 37 | `; 38 | 39 | exports[`snapshot test - title only 1`] = ` 40 | Array [ 41 | Object { 42 | "height": 1, 43 | "properties": Object { 44 | "background": undefined, 45 | "markdown": "# text", 46 | }, 47 | "type": "text", 48 | "width": 24, 49 | "x": undefined, 50 | "y": undefined, 51 | }, 52 | ] 53 | `; 54 | -------------------------------------------------------------------------------- /test/dashboard/widget/__snapshots__/KeyValueTableWidget.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`snapshot test 1`] = ` 4 | Array [ 5 | Object { 6 | "height": 3, 7 | "properties": Object { 8 | "background": undefined, 9 | "markdown": "| name| has 10 | |---|--- 11 | | John Wick| a dog", 12 | }, 13 | "type": "text", 14 | "width": 24, 15 | "x": undefined, 16 | "y": undefined, 17 | }, 18 | ] 19 | `; 20 | -------------------------------------------------------------------------------- /test/dashboard/widget/__snapshots__/KeyValueTableWidgetV2.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`snapshot test 1`] = ` 4 | Array [ 5 | Object { 6 | "height": 3, 7 | "properties": Object { 8 | "background": undefined, 9 | "markdown": "| name| has 10 | |---|--- 11 | | John Wick| a dog", 12 | }, 13 | "type": "text", 14 | "width": 24, 15 | "x": undefined, 16 | "y": undefined, 17 | }, 18 | ] 19 | `; 20 | -------------------------------------------------------------------------------- /test/dashboard/widget/__snapshots__/MonitoringHeaderWidget.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`snapshot test: title and family 1`] = ` 4 | Array [ 5 | Object { 6 | "height": 1, 7 | "properties": Object { 8 | "background": undefined, 9 | "markdown": "### DummyFamily **DummyTitle**", 10 | }, 11 | "type": "text", 12 | "width": 24, 13 | "x": undefined, 14 | "y": undefined, 15 | }, 16 | ] 17 | `; 18 | 19 | exports[`snapshot test: title only 1`] = ` 20 | Array [ 21 | Object { 22 | "height": 1, 23 | "properties": Object { 24 | "background": undefined, 25 | "markdown": "### DummyTitle", 26 | }, 27 | "type": "text", 28 | "width": 24, 29 | "x": undefined, 30 | "y": undefined, 31 | }, 32 | ] 33 | `; 34 | 35 | exports[`snapshot test: with link 1`] = ` 36 | Array [ 37 | Object { 38 | "height": 1, 39 | "properties": Object { 40 | "background": undefined, 41 | "markdown": "### DummyFamily **[DummyTitle](http://amazon.com)**", 42 | }, 43 | "type": "text", 44 | "width": 24, 45 | "x": undefined, 46 | "y": undefined, 47 | }, 48 | ] 49 | `; 50 | 51 | exports[`snapshot test: with link and custom text 1`] = ` 52 | Array [ 53 | Object { 54 | "height": 1, 55 | "properties": Object { 56 | "background": undefined, 57 | "markdown": "### DummyFamily **[DummyTitle](http://amazon.com)**", 58 | }, 59 | "type": "text", 60 | "width": 24, 61 | "x": undefined, 62 | "y": undefined, 63 | }, 64 | ] 65 | `; 66 | 67 | exports[`snapshot test: with link, custom text and description 1`] = ` 68 | Array [ 69 | Object { 70 | "height": 2, 71 | "properties": Object { 72 | "background": undefined, 73 | "markdown": "### DummyFamily **[DummyTitle](http://amazon.com)** 74 | 75 | CustomDescription", 76 | }, 77 | "type": "text", 78 | "width": 24, 79 | "x": undefined, 80 | "y": undefined, 81 | }, 82 | ] 83 | `; 84 | 85 | exports[`snapshot test: with link, custom text and description with custom height 1`] = ` 86 | Array [ 87 | Object { 88 | "height": 11, 89 | "properties": Object { 90 | "background": undefined, 91 | "markdown": "### DummyFamily **[DummyTitle](http://amazon.com)** 92 | 93 | CustomDescription", 94 | }, 95 | "type": "text", 96 | "width": 24, 97 | "x": undefined, 98 | "y": undefined, 99 | }, 100 | ] 101 | `; 102 | -------------------------------------------------------------------------------- /test/dashboard/widget/__snapshots__/UnofficialWidgets.test.ts.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`AlarmSummaryMatrixWidget: snapshot test 1`] = ` 4 | Array [ 5 | Object { 6 | "height": 2, 7 | "properties": Object { 8 | "alarms": Array [ 9 | "arn:aws:cloudwatch:us-west-2:123456789012:alarm:TestAlarm1", 10 | "arn:aws:cloudwatch:us-west-2:123456789012:alarm:TestAlarm2", 11 | "arn:aws:cloudwatch:us-west-2:123456789012:alarm:TestAlarm3", 12 | ], 13 | "title": undefined, 14 | }, 15 | "type": "alarm", 16 | "width": 24, 17 | "x": undefined, 18 | "y": undefined, 19 | }, 20 | ] 21 | `; 22 | -------------------------------------------------------------------------------- /test/monitoring/TestMonitoringScope.ts: -------------------------------------------------------------------------------- 1 | import { AlarmWidget, IWidget } from "aws-cdk-lib/aws-cloudwatch"; 2 | import { Construct } from "constructs"; 3 | 4 | import { 5 | AlarmFactory, 6 | AlarmFactoryDefaults, 7 | AlarmWithAnnotation, 8 | AwsConsoleUrlFactory, 9 | IWidgetFactory, 10 | MetricFactory, 11 | MetricFactoryDefaults, 12 | MonitoringScope, 13 | noopAction, 14 | } from "../../lib"; 15 | 16 | const DummyAwsAccountId = "1234567890"; 17 | const DummyAwsAccountRegion = "eu-west-1"; 18 | const DummyGlobalMetricDefaults: MetricFactoryDefaults = { 19 | namespace: "DummyNamespace", 20 | }; 21 | const DummyGlobalAlarmDefaults: AlarmFactoryDefaults = { 22 | alarmNamePrefix: "Test", 23 | datapointsToAlarm: 3, 24 | actionsEnabled: true, 25 | action: noopAction(), 26 | }; 27 | const DefaultAlarmWidgetWidth = 24 / 4; 28 | const DefaultAlarmWidgetHeight = 24 / 6; 29 | 30 | class TestAlarmFactory extends AlarmFactory { 31 | constructor(scope: Construct, localAlarmNamePrefix: string) { 32 | super(scope, { 33 | localAlarmNamePrefix, 34 | globalAlarmDefaults: DummyGlobalAlarmDefaults, 35 | globalMetricDefaults: DummyGlobalMetricDefaults, 36 | }); 37 | } 38 | } 39 | 40 | class TestWidgetFactory implements IWidgetFactory { 41 | createAlarmDetailWidget(alarm: AlarmWithAnnotation): IWidget { 42 | return new AlarmWidget({ 43 | alarm: alarm.alarm, 44 | width: DefaultAlarmWidgetWidth, 45 | height: DefaultAlarmWidgetHeight, 46 | }); 47 | } 48 | } 49 | 50 | export class TestMonitoringScope extends MonitoringScope { 51 | constructor(scope: Construct, id: string) { 52 | super(scope, id); 53 | } 54 | 55 | createAlarmFactory(alarmNamePrefix: string): AlarmFactory { 56 | return new TestAlarmFactory(this, alarmNamePrefix); 57 | } 58 | 59 | createAwsConsoleUrlFactory(): AwsConsoleUrlFactory { 60 | return new AwsConsoleUrlFactory({ 61 | awsAccountId: DummyAwsAccountId, 62 | awsAccountRegion: DummyAwsAccountRegion, 63 | }); 64 | } 65 | 66 | createMetricFactory(): MetricFactory { 67 | return new MetricFactory(this, { 68 | globalDefaults: DummyGlobalMetricDefaults, 69 | }); 70 | } 71 | 72 | createWidgetFactory(): IWidgetFactory { 73 | return new TestWidgetFactory(); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /test/monitoring/aws-acm/CertificateManagerMonitoring.test.ts: -------------------------------------------------------------------------------- 1 | import { Stack } from "aws-cdk-lib"; 2 | import { Template } from "aws-cdk-lib/assertions"; 3 | import { Certificate } from "aws-cdk-lib/aws-certificatemanager"; 4 | 5 | import { CertificateManagerMonitoring } from "../../../lib"; 6 | import { addMonitoringDashboardsToStack } from "../../utils/SnapshotUtil"; 7 | import { TestMonitoringScope } from "../TestMonitoringScope"; 8 | 9 | test("snapshot test: no alarms", () => { 10 | const stack = new Stack(); 11 | 12 | const certificate = new Certificate(stack, "Certificate1", { 13 | domainName: "www.monitoring.cdk", 14 | }); 15 | 16 | const scope = new TestMonitoringScope(stack, "Scope"); 17 | 18 | const monitoring = new CertificateManagerMonitoring(scope, { 19 | alarmFriendlyName: "Certificates", 20 | certificate, 21 | }); 22 | 23 | addMonitoringDashboardsToStack(stack, monitoring); 24 | expect(Template.fromStack(stack)).toMatchSnapshot(); 25 | }); 26 | 27 | test("snapshot test: all alarms", () => { 28 | const stack = new Stack(); 29 | 30 | const scope = new TestMonitoringScope(stack, "Scope"); 31 | 32 | const certificate = new Certificate(stack, "Certificate1", { 33 | domainName: "www.monitoring.cdk", 34 | }); 35 | 36 | let numAlarmsCreated = 0; 37 | 38 | const monitoring = new CertificateManagerMonitoring(scope, { 39 | alarmFriendlyName: "Certificates", 40 | certificate, 41 | addDaysToExpiryAlarm: { 42 | Warning: { 43 | minDaysToExpiry: 14, 44 | }, 45 | }, 46 | useCreatedAlarms: { 47 | consume(alarms) { 48 | numAlarmsCreated = alarms.length; 49 | }, 50 | }, 51 | }); 52 | 53 | addMonitoringDashboardsToStack(stack, monitoring); 54 | expect(numAlarmsCreated).toStrictEqual(1); 55 | expect(Template.fromStack(stack)).toMatchSnapshot(); 56 | }); 57 | -------------------------------------------------------------------------------- /test/monitoring/aws-billing/BillingMonitoring.test.ts: -------------------------------------------------------------------------------- 1 | import { Stack } from "aws-cdk-lib"; 2 | import { Template } from "aws-cdk-lib/assertions"; 3 | 4 | import { AlarmWithAnnotation, BillingMonitoring } from "../../../lib"; 5 | import { addMonitoringDashboardsToStack } from "../../utils/SnapshotUtil"; 6 | import { TestMonitoringScope } from "../TestMonitoringScope"; 7 | 8 | test("snapshot test", () => { 9 | const stack = new Stack(); 10 | 11 | const scope = new TestMonitoringScope(stack, "Scope"); 12 | 13 | const monitoring = new BillingMonitoring(scope, { 14 | humanReadableName: "Billing", 15 | }); 16 | 17 | addMonitoringDashboardsToStack(stack, monitoring); 18 | expect(Template.fromStack(stack)).toMatchSnapshot(); 19 | }); 20 | 21 | test("snapshot test: all alarms", () => { 22 | const stack = new Stack(); 23 | 24 | const scope = new TestMonitoringScope(stack, "Scope"); 25 | 26 | let numAlarmsCreated = 0; 27 | 28 | const monitoring = new BillingMonitoring(scope, { 29 | humanReadableName: "Billing", 30 | addTotalCostAnomalyAlarm: { 31 | Warning: { 32 | alarmWhenBelowTheBand: false, 33 | alarmWhenAboveTheBand: true, 34 | standardDeviationForAlarm: 5, 35 | }, 36 | }, 37 | useCreatedAlarms: { 38 | consume(alarms: AlarmWithAnnotation[]) { 39 | numAlarmsCreated = alarms.length; 40 | }, 41 | }, 42 | }); 43 | 44 | addMonitoringDashboardsToStack(stack, monitoring); 45 | expect(numAlarmsCreated).toStrictEqual(1); 46 | expect(Template.fromStack(stack)).toMatchSnapshot(); 47 | }); 48 | -------------------------------------------------------------------------------- /test/monitoring/aws-cloudfront/CloudFrontDistribution.test.ts: -------------------------------------------------------------------------------- 1 | import { Stack } from "aws-cdk-lib"; 2 | import { Template } from "aws-cdk-lib/assertions"; 3 | import { Distribution } from "aws-cdk-lib/aws-cloudfront"; 4 | import { S3Origin } from "aws-cdk-lib/aws-cloudfront-origins"; 5 | import { Bucket } from "aws-cdk-lib/aws-s3"; 6 | 7 | import { 8 | AlarmWithAnnotation, 9 | CloudFrontDistributionMonitoring, 10 | } from "../../../lib"; 11 | import { addMonitoringDashboardsToStack } from "../../utils/SnapshotUtil"; 12 | import { TestMonitoringScope } from "../TestMonitoringScope"; 13 | 14 | [undefined, true, false].forEach((additionalMetricsEnabled) => { 15 | test(`snapshot test: no alarms with additionalMetricsEnabled=${additionalMetricsEnabled}`, () => { 16 | const stack = new Stack(); 17 | const bucket = new Bucket(stack, "Bucket"); 18 | const distribution = new Distribution(stack, "Distribution", { 19 | defaultBehavior: { 20 | origin: new S3Origin(bucket), 21 | }, 22 | }); 23 | 24 | const scope = new TestMonitoringScope(stack, "Scope"); 25 | 26 | const monitoring = new CloudFrontDistributionMonitoring(scope, { 27 | distribution, 28 | additionalMetricsEnabled, 29 | }); 30 | 31 | addMonitoringDashboardsToStack(stack, monitoring); 32 | expect(Template.fromStack(stack)).toMatchSnapshot(); 33 | }); 34 | 35 | test(`snapshot test: all alarms with additionalMetricsEnabled=${additionalMetricsEnabled}`, () => { 36 | const stack = new Stack(undefined, undefined, { 37 | env: { 38 | region: "us-east-1", 39 | }, 40 | }); 41 | const bucket = new Bucket(stack, "Bucket"); 42 | const distribution = new Distribution(stack, "Distribution", { 43 | defaultBehavior: { 44 | origin: new S3Origin(bucket), 45 | }, 46 | }); 47 | 48 | const scope = new TestMonitoringScope(stack, "Scope"); 49 | 50 | let numAlarmsCreated = 0; 51 | 52 | const monitoring = new CloudFrontDistributionMonitoring(scope, { 53 | distribution, 54 | additionalMetricsEnabled, 55 | addLowTpsAlarm: { 56 | Warning: { 57 | minTps: 10, 58 | }, 59 | }, 60 | addHighTpsAlarm: { 61 | Warning: { 62 | maxTps: 20, 63 | }, 64 | }, 65 | addError4xxRate: { 66 | Warning: { 67 | maxErrorRate: 0.5, 68 | }, 69 | }, 70 | addFault5xxRate: { 71 | Warning: { 72 | maxErrorRate: 0.8, 73 | }, 74 | }, 75 | useCreatedAlarms: { 76 | consume(alarms: AlarmWithAnnotation[]) { 77 | numAlarmsCreated = alarms.length; 78 | }, 79 | }, 80 | }); 81 | 82 | addMonitoringDashboardsToStack(stack, monitoring); 83 | expect(numAlarmsCreated).toStrictEqual(4); 84 | expect(Template.fromStack(stack)).toMatchSnapshot(); 85 | }); 86 | }); 87 | -------------------------------------------------------------------------------- /test/monitoring/aws-codebuild/CodeBuildProjectMonitoring.test.ts: -------------------------------------------------------------------------------- 1 | import { Duration, Stack } from "aws-cdk-lib"; 2 | import { Template } from "aws-cdk-lib/assertions"; 3 | import { BuildSpec, Project } from "aws-cdk-lib/aws-codebuild"; 4 | 5 | import { AlarmWithAnnotation, CodeBuildProjectMonitoring } from "../../../lib"; 6 | import { addMonitoringDashboardsToStack } from "../../utils/SnapshotUtil"; 7 | import { TestMonitoringScope } from "../TestMonitoringScope"; 8 | 9 | test("snapshot test: all alarms", () => { 10 | const stack = new Stack(); 11 | 12 | const scope = new TestMonitoringScope(stack, "Scope"); 13 | 14 | const project = new Project(stack, "Project", { 15 | projectName: "TheProject", 16 | buildSpec: BuildSpec.fromObject({ 17 | version: "0.2", 18 | phases: { 19 | build: { 20 | commands: ["echo hello"], 21 | }, 22 | }, 23 | }), 24 | }); 25 | 26 | let numAlarmsCreated = 0; 27 | 28 | const monitoring = new CodeBuildProjectMonitoring(scope, { 29 | project, 30 | addDurationP99Alarm: { 31 | Warning: { 32 | maxDuration: Duration.minutes(10), 33 | }, 34 | }, 35 | addDurationP90Alarm: { 36 | Warning: { 37 | maxDuration: Duration.minutes(2), 38 | }, 39 | }, 40 | addDurationP50Alarm: { 41 | Warning: { 42 | maxDuration: Duration.minutes(1), 43 | }, 44 | }, 45 | addFailedBuildCountAlarm: { 46 | Warning: { 47 | maxErrorCount: 1, 48 | }, 49 | }, 50 | addFailedBuildRateAlarm: { 51 | Warning: { 52 | maxErrorRate: 0.1, 53 | }, 54 | }, 55 | useCreatedAlarms: { 56 | consume(alarms: AlarmWithAnnotation[]) { 57 | numAlarmsCreated = alarms.length; 58 | }, 59 | }, 60 | }); 61 | 62 | addMonitoringDashboardsToStack(stack, monitoring); 63 | expect(numAlarmsCreated).toStrictEqual(5); 64 | expect(Template.fromStack(stack)).toMatchSnapshot(); 65 | }); 66 | -------------------------------------------------------------------------------- /test/monitoring/aws-docdb/DocumentDbMonitoring.test.ts: -------------------------------------------------------------------------------- 1 | import { Stack } from "aws-cdk-lib"; 2 | import { Template } from "aws-cdk-lib/assertions"; 3 | import { DatabaseCluster } from "aws-cdk-lib/aws-docdb"; 4 | import { 5 | InstanceClass, 6 | InstanceSize, 7 | InstanceType, 8 | Vpc, 9 | } from "aws-cdk-lib/aws-ec2"; 10 | 11 | import { DocumentDbMonitoring } from "../../../lib"; 12 | import { addMonitoringDashboardsToStack } from "../../utils/SnapshotUtil"; 13 | import { TestMonitoringScope } from "../TestMonitoringScope"; 14 | 15 | test("snapshot test: no alarms", () => { 16 | const stack = new Stack(); 17 | const scope = new TestMonitoringScope(stack, "Scope"); 18 | const cluster = new DatabaseCluster(stack, "DummyDocDbCluster", { 19 | vpc: new Vpc(stack, "Vpc"), 20 | masterUser: { username: "master" }, 21 | instanceType: InstanceType.of(InstanceClass.C5, InstanceSize.LARGE), 22 | }); 23 | 24 | const monitoring = new DocumentDbMonitoring(scope, { 25 | alarmFriendlyName: "DummyDocDbCluster", 26 | cluster, 27 | }); 28 | 29 | addMonitoringDashboardsToStack(stack, monitoring); 30 | expect(Template.fromStack(stack)).toMatchSnapshot(); 31 | }); 32 | 33 | test("snapshot test: all alarms", () => { 34 | const stack = new Stack(); 35 | const scope = new TestMonitoringScope(stack, "Scope"); 36 | const cluster = new DatabaseCluster(stack, "DummyDocDbCluster", { 37 | vpc: new Vpc(stack, "Vpc"), 38 | masterUser: { username: "master" }, 39 | instanceType: InstanceType.of(InstanceClass.C5, InstanceSize.LARGE), 40 | }); 41 | 42 | let numAlarmsCreated = 0; 43 | 44 | const monitoring = new DocumentDbMonitoring(scope, { 45 | cluster, 46 | addCpuUsageAlarm: { 47 | Warning: { 48 | maxUsagePercent: 70, 49 | }, 50 | }, 51 | useCreatedAlarms: { 52 | consume(alarms) { 53 | numAlarmsCreated = alarms.length; 54 | }, 55 | }, 56 | }); 57 | 58 | addMonitoringDashboardsToStack(stack, monitoring); 59 | expect(numAlarmsCreated).toStrictEqual(1); 60 | expect(Template.fromStack(stack)).toMatchSnapshot(); 61 | }); 62 | -------------------------------------------------------------------------------- /test/monitoring/aws-ec2/AutoScalingGroupMonitoring.test.ts: -------------------------------------------------------------------------------- 1 | import { Stack } from "aws-cdk-lib"; 2 | import { Template } from "aws-cdk-lib/assertions"; 3 | import { AutoScalingGroup } from "aws-cdk-lib/aws-autoscaling"; 4 | import { 5 | InstanceClass, 6 | InstanceSize, 7 | InstanceType, 8 | MachineImage, 9 | Vpc, 10 | } from "aws-cdk-lib/aws-ec2"; 11 | 12 | import { AutoScalingGroupMonitoring } from "../../../lib"; 13 | import { addMonitoringDashboardsToStack } from "../../utils/SnapshotUtil"; 14 | import { TestMonitoringScope } from "../TestMonitoringScope"; 15 | 16 | test("snapshot test", () => { 17 | const stack = new Stack(); 18 | 19 | const scope = new TestMonitoringScope(stack, "Scope"); 20 | 21 | const vpc = new Vpc(stack, "Vpc"); 22 | 23 | const autoScalingGroup = new AutoScalingGroup(stack, "ASG", { 24 | autoScalingGroupName: "DummyASG", 25 | vpc, 26 | instanceType: InstanceType.of(InstanceClass.M4, InstanceSize.LARGE), 27 | machineImage: MachineImage.latestAmazonLinux2023(), 28 | minCapacity: 1, 29 | maxCapacity: 10, 30 | desiredCapacity: 5, 31 | }); 32 | 33 | const monitoring = new AutoScalingGroupMonitoring(scope, { 34 | autoScalingGroup, 35 | }); 36 | 37 | addMonitoringDashboardsToStack(stack, monitoring); 38 | expect(Template.fromStack(stack)).toMatchSnapshot(); 39 | }); 40 | -------------------------------------------------------------------------------- /test/monitoring/aws-ec2/EC2Monitoring.test.ts: -------------------------------------------------------------------------------- 1 | import { Stack } from "aws-cdk-lib"; 2 | import { Template } from "aws-cdk-lib/assertions"; 3 | import { AutoScalingGroup } from "aws-cdk-lib/aws-autoscaling"; 4 | 5 | import { EC2Monitoring } from "../../../lib"; 6 | import { addMonitoringDashboardsToStack } from "../../utils/SnapshotUtil"; 7 | import { TestMonitoringScope } from "../TestMonitoringScope"; 8 | 9 | test("snapshot test: all instances, no alarms", () => { 10 | const stack = new Stack(); 11 | 12 | const scope = new TestMonitoringScope(stack, "Scope"); 13 | 14 | const monitoring = new EC2Monitoring(scope, { 15 | alarmFriendlyName: "EC2", 16 | }); 17 | 18 | addMonitoringDashboardsToStack(stack, monitoring); 19 | expect(Template.fromStack(stack)).toMatchSnapshot(); 20 | }); 21 | 22 | test("snapshot test: ASG, no alarms", () => { 23 | const stack = new Stack(); 24 | 25 | const scope = new TestMonitoringScope(stack, "Scope"); 26 | 27 | const monitoring = new EC2Monitoring(scope, { 28 | alarmFriendlyName: "EC2", 29 | autoScalingGroup: AutoScalingGroup.fromAutoScalingGroupName( 30 | stack, 31 | "DummyASG", 32 | "DummyASG", 33 | ), 34 | }); 35 | 36 | addMonitoringDashboardsToStack(stack, monitoring); 37 | expect(Template.fromStack(stack)).toMatchSnapshot(); 38 | }); 39 | 40 | test("snapshot test: instance filter, no alarms", () => { 41 | const stack = new Stack(); 42 | 43 | const scope = new TestMonitoringScope(stack, "Scope"); 44 | 45 | const monitoring = new EC2Monitoring(scope, { 46 | alarmFriendlyName: "EC2", 47 | instanceIds: ["instance1", "instance2"], 48 | }); 49 | 50 | addMonitoringDashboardsToStack(stack, monitoring); 51 | expect(Template.fromStack(stack)).toMatchSnapshot(); 52 | }); 53 | 54 | test("snapshot test: instance filter + ASG, no alarms", () => { 55 | const stack = new Stack(); 56 | 57 | const scope = new TestMonitoringScope(stack, "Scope"); 58 | 59 | const monitoring = new EC2Monitoring(scope, { 60 | alarmFriendlyName: "EC2", 61 | instanceIds: ["instance1", "instance2"], 62 | autoScalingGroup: AutoScalingGroup.fromAutoScalingGroupName( 63 | stack, 64 | "DummyASG", 65 | "DummyASG", 66 | ), 67 | }); 68 | 69 | addMonitoringDashboardsToStack(stack, monitoring); 70 | expect(Template.fromStack(stack)).toMatchSnapshot(); 71 | }); 72 | -------------------------------------------------------------------------------- /test/monitoring/aws-glue/GlueJobMonitoring.test.ts: -------------------------------------------------------------------------------- 1 | import { Stack } from "aws-cdk-lib"; 2 | import { Template } from "aws-cdk-lib/assertions"; 3 | 4 | import { AlarmWithAnnotation, GlueJobMonitoring } from "../../../lib"; 5 | import { addMonitoringDashboardsToStack } from "../../utils/SnapshotUtil"; 6 | import { TestMonitoringScope } from "../TestMonitoringScope"; 7 | 8 | test("snapshot test: no alarms", () => { 9 | const stack = new Stack(); 10 | 11 | const scope = new TestMonitoringScope(stack, "Scope"); 12 | 13 | const monitoring = new GlueJobMonitoring(scope, { 14 | jobName: "DummyGlueJob", 15 | }); 16 | 17 | addMonitoringDashboardsToStack(stack, monitoring); 18 | expect(Template.fromStack(stack)).toMatchSnapshot(); 19 | }); 20 | 21 | test("snapshot test: all alarms", () => { 22 | const stack = new Stack(); 23 | 24 | const scope = new TestMonitoringScope(stack, "Scope"); 25 | 26 | let numAlarmsCreated = 0; 27 | 28 | const monitoring = new GlueJobMonitoring(scope, { 29 | jobName: "DummyGlueJob", 30 | alarmFriendlyName: "DummyApi", 31 | addKilledTaskCountAlarm: { 32 | Warning: { 33 | maxErrorCount: 3, 34 | }, 35 | }, 36 | addKilledTaskRateAlarm: { 37 | Warning: { 38 | maxErrorRate: 2, 39 | }, 40 | }, 41 | addFailedTaskCountAlarm: { 42 | Warning: { 43 | maxErrorCount: 3, 44 | }, 45 | }, 46 | addFailedTaskRateAlarm: { 47 | Warning: { 48 | maxErrorRate: 4, 49 | }, 50 | }, 51 | useCreatedAlarms: { 52 | consume(alarms: AlarmWithAnnotation[]) { 53 | numAlarmsCreated = alarms.length; 54 | }, 55 | }, 56 | }); 57 | 58 | expect(numAlarmsCreated).toStrictEqual(4); 59 | addMonitoringDashboardsToStack(stack, monitoring); 60 | expect(Template.fromStack(stack)).toMatchSnapshot(); 61 | }); 62 | -------------------------------------------------------------------------------- /test/monitoring/aws-kinesis/KinesisDataStreamMonitoring.test.ts: -------------------------------------------------------------------------------- 1 | import { Stack } from "aws-cdk-lib"; 2 | import { Template } from "aws-cdk-lib/assertions"; 3 | 4 | import { KinesisDataStreamMonitoring } from "../../../lib"; 5 | import { addMonitoringDashboardsToStack } from "../../utils/SnapshotUtil"; 6 | import { TestMonitoringScope } from "../TestMonitoringScope"; 7 | 8 | test("snapshot test for stream: no alarms", () => { 9 | const stack = new Stack(); 10 | 11 | const scope = new TestMonitoringScope(stack, "Scope"); 12 | 13 | const monitoring = new KinesisDataStreamMonitoring(scope, { 14 | streamName: "my-kinesis-data-stream", 15 | }); 16 | 17 | addMonitoringDashboardsToStack(stack, monitoring); 18 | expect(Template.fromStack(stack)).toMatchSnapshot(); 19 | }); 20 | 21 | test("snapshot test for stream: all alarms", () => { 22 | const stack = new Stack(); 23 | 24 | const scope = new TestMonitoringScope(stack, "Scope"); 25 | 26 | let numAlarmsCreated = 0; 27 | 28 | const monitoring = new KinesisDataStreamMonitoring(scope, { 29 | streamName: "my-kinesis-data-stream", 30 | addIteratorMaxAgeAlarm: { 31 | Warning: { 32 | maxAgeInMillis: 1_000_000, 33 | }, 34 | }, 35 | addPutRecordsThrottledAlarm: { 36 | Critical: { 37 | maxRecordsThrottledThreshold: 5, 38 | }, 39 | }, 40 | addPutRecordsFailedAlarm: { 41 | Critical: { 42 | maxRecordsFailedThreshold: 5, 43 | }, 44 | }, 45 | addReadProvisionedThroughputExceededAlarm: { 46 | Critical: { 47 | maxRecordsThrottledThreshold: 0, 48 | }, 49 | }, 50 | addWriteProvisionedThroughputExceededAlarm: { 51 | Critical: { 52 | maxRecordsThrottledThreshold: 0, 53 | }, 54 | }, 55 | useCreatedAlarms: { 56 | consume(alarms) { 57 | numAlarmsCreated = alarms.length; 58 | }, 59 | }, 60 | }); 61 | 62 | expect(numAlarmsCreated).toStrictEqual(5); 63 | addMonitoringDashboardsToStack(stack, monitoring); 64 | expect(Template.fromStack(stack)).toMatchSnapshot(); 65 | }); 66 | -------------------------------------------------------------------------------- /test/monitoring/aws-kinesisanalytics/KinesisDataAnalyticsMonitoring.test.ts: -------------------------------------------------------------------------------- 1 | import { Stack } from "aws-cdk-lib"; 2 | import { Template } from "aws-cdk-lib/assertions"; 3 | 4 | import { KinesisDataAnalyticsMonitoring } from "../../../lib"; 5 | import { addMonitoringDashboardsToStack } from "../../utils/SnapshotUtil"; 6 | import { TestMonitoringScope } from "../TestMonitoringScope"; 7 | 8 | test("snapshot test: no alarms", () => { 9 | const stack = new Stack(); 10 | 11 | const scope = new TestMonitoringScope(stack, "Scope"); 12 | 13 | const monitoring = new KinesisDataAnalyticsMonitoring(scope, { 14 | application: "DummyApplication", 15 | }); 16 | 17 | addMonitoringDashboardsToStack(stack, monitoring); 18 | expect(Template.fromStack(stack)).toMatchSnapshot(); 19 | }); 20 | 21 | test("snapshot test: all alarms", () => { 22 | const stack = new Stack(); 23 | 24 | const scope = new TestMonitoringScope(stack, "Scope"); 25 | 26 | let numAlarmsCreated = 0; 27 | 28 | const monitoring = new KinesisDataAnalyticsMonitoring(scope, { 29 | application: "DummyApplication", 30 | addDowntimeAlarm: { 31 | Warning: { 32 | maxDowntimeInMillis: 300_000, 33 | }, 34 | }, 35 | addFullRestartCountAlarm: { 36 | Warning: { 37 | maxFullRestartCount: 1, 38 | }, 39 | }, 40 | useCreatedAlarms: { 41 | consume(alarms) { 42 | numAlarmsCreated = alarms.length; 43 | }, 44 | }, 45 | }); 46 | 47 | addMonitoringDashboardsToStack(stack, monitoring); 48 | expect(numAlarmsCreated).toStrictEqual(2); 49 | expect(Template.fromStack(stack)).toMatchSnapshot(); 50 | }); 51 | -------------------------------------------------------------------------------- /test/monitoring/aws-rds/AuroraClusterMonitoring.test.ts: -------------------------------------------------------------------------------- 1 | import { Stack } from "aws-cdk-lib"; 2 | import { Template } from "aws-cdk-lib/assertions"; 3 | import { DatabaseClusterEngine, ServerlessCluster } from "aws-cdk-lib/aws-rds"; 4 | 5 | import { AuroraClusterMonitoring } from "../../../lib"; 6 | import { addMonitoringDashboardsToStack } from "../../utils/SnapshotUtil"; 7 | import { TestMonitoringScope } from "../TestMonitoringScope"; 8 | 9 | test("snapshot test: no alarms", () => { 10 | const stack = new Stack(); 11 | 12 | const scope = new TestMonitoringScope(stack, "Scope"); 13 | 14 | const auroraCluster = new ServerlessCluster(scope, "DummyAuroraCluster", { 15 | clusterIdentifier: "my-aurora-cluster", 16 | engine: DatabaseClusterEngine.AURORA_MYSQL, 17 | }); 18 | 19 | const monitoring = new AuroraClusterMonitoring(scope, { 20 | alarmFriendlyName: "DummyAuroraCluster", 21 | cluster: auroraCluster, 22 | }); 23 | 24 | addMonitoringDashboardsToStack(stack, monitoring); 25 | expect(Template.fromStack(stack)).toMatchSnapshot(); 26 | }); 27 | 28 | test("snapshot test: all alarms", () => { 29 | const stack = new Stack(); 30 | 31 | const scope = new TestMonitoringScope(stack, "Scope"); 32 | 33 | const auroraCluster = new ServerlessCluster(scope, "DummyAuroraCluster", { 34 | clusterIdentifier: "my-aurora-cluster", 35 | engine: DatabaseClusterEngine.AURORA_MYSQL, 36 | }); 37 | 38 | let numAlarmsCreated = 0; 39 | 40 | const monitoring = new AuroraClusterMonitoring(scope, { 41 | alarmFriendlyName: "DummyAuroraCluster", 42 | cluster: auroraCluster, 43 | addCpuUsageAlarm: { 44 | Warning: { 45 | maxUsagePercent: 80, 46 | }, 47 | }, 48 | addMaxConnectionCountAlarm: { 49 | Warning: { 50 | maxConnectionCount: 100, 51 | }, 52 | }, 53 | addMinConnectionCountAlarm: { 54 | Warning: { 55 | minConnectionCount: 0, 56 | }, 57 | }, 58 | addMaxServerlessDatabaseCapacityAlarm: { 59 | Warning: { 60 | maxUsageCount: 100, 61 | }, 62 | }, 63 | useCreatedAlarms: { 64 | consume(alarms) { 65 | numAlarmsCreated = alarms.length; 66 | }, 67 | }, 68 | }); 69 | 70 | addMonitoringDashboardsToStack(stack, monitoring); 71 | expect(numAlarmsCreated).toStrictEqual(4); 72 | expect(Template.fromStack(stack)).toMatchSnapshot(); 73 | }); 74 | -------------------------------------------------------------------------------- /test/monitoring/aws-rds/RdsClusterMonitoring.test.ts: -------------------------------------------------------------------------------- 1 | import { Stack } from "aws-cdk-lib"; 2 | import { Template } from "aws-cdk-lib/assertions"; 3 | 4 | import { RdsClusterMonitoring } from "../../../lib"; 5 | import { addMonitoringDashboardsToStack } from "../../utils/SnapshotUtil"; 6 | import { TestMonitoringScope } from "../TestMonitoringScope"; 7 | 8 | test("snapshot test: no alarms", () => { 9 | const stack = new Stack(); 10 | 11 | const scope = new TestMonitoringScope(stack, "Scope"); 12 | 13 | const monitoring = new RdsClusterMonitoring(scope, { 14 | alarmFriendlyName: "DummyRdsCluster", 15 | clusterIdentifier: "my-rds-cluster", 16 | }); 17 | 18 | addMonitoringDashboardsToStack(stack, monitoring); 19 | expect(Template.fromStack(stack)).toMatchSnapshot(); 20 | }); 21 | 22 | test("snapshot test: all alarms", () => { 23 | const stack = new Stack(); 24 | 25 | const scope = new TestMonitoringScope(stack, "Scope"); 26 | 27 | let numAlarmsCreated = 0; 28 | 29 | const monitoring = new RdsClusterMonitoring(scope, { 30 | clusterIdentifier: "my-rds-cluster", 31 | addDiskSpaceUsageAlarm: { 32 | Warning: { 33 | maxUsagePercent: 80, 34 | }, 35 | }, 36 | addCpuUsageAlarm: { 37 | Warning: { 38 | maxUsagePercent: 70, 39 | }, 40 | }, 41 | addMinConnectionCountAlarm: { 42 | Warning: { 43 | minConnectionCount: 1, 44 | }, 45 | }, 46 | addMaxConnectionCountAlarm: { 47 | Warning: { 48 | maxConnectionCount: 100, 49 | }, 50 | }, 51 | useCreatedAlarms: { 52 | consume(alarms) { 53 | numAlarmsCreated = alarms.length; 54 | }, 55 | }, 56 | }); 57 | 58 | addMonitoringDashboardsToStack(stack, monitoring); 59 | expect(numAlarmsCreated).toStrictEqual(4); 60 | expect(Template.fromStack(stack)).toMatchSnapshot(); 61 | }); 62 | -------------------------------------------------------------------------------- /test/monitoring/aws-rds/RdsInstanceMonitoring.test.ts: -------------------------------------------------------------------------------- 1 | import { Stack } from "aws-cdk-lib"; 2 | import { Template } from "aws-cdk-lib/assertions"; 3 | import { Vpc } from "aws-cdk-lib/aws-ec2"; 4 | import { DatabaseInstance, DatabaseInstanceEngine } from "aws-cdk-lib/aws-rds"; 5 | 6 | import { RdsInstanceMonitoring } from "../../../lib"; 7 | import { addMonitoringDashboardsToStack } from "../../utils/SnapshotUtil"; 8 | import { TestMonitoringScope } from "../TestMonitoringScope"; 9 | 10 | function createRdsInstance() { 11 | const stack = new Stack(); 12 | const vpc = new Vpc(stack, "Vpc"); 13 | const instance = new DatabaseInstance(stack, "DatabaseInstance", { 14 | instanceIdentifier: "my-rds-instance", 15 | engine: DatabaseInstanceEngine.MYSQL, 16 | vpc: vpc, 17 | }); 18 | return { instance, stack }; 19 | } 20 | 21 | test.each([createRdsInstance])("snapshot test: no alarms - %#", (factory) => { 22 | const { stack, instance } = factory(); 23 | 24 | const scope = new TestMonitoringScope(stack, "Scope"); 25 | const monitoring = new RdsInstanceMonitoring(scope, { 26 | alarmFriendlyName: "DummyRdsInstance", 27 | instance: instance, 28 | }); 29 | 30 | addMonitoringDashboardsToStack(stack, monitoring); 31 | expect(Template.fromStack(stack)).toMatchSnapshot(); 32 | }); 33 | 34 | test.each([createRdsInstance])("snapshot test: all alarms - %#", (factory) => { 35 | const { stack, instance } = factory(); 36 | 37 | const scope = new TestMonitoringScope(stack, "Scope"); 38 | 39 | let numAlarmsCreated = 0; 40 | 41 | const monitoring = new RdsInstanceMonitoring(scope, { 42 | instance: instance, 43 | addFreeStorageSpaceAlarm: { 44 | Warning: { 45 | minCount: 20, 46 | }, 47 | }, 48 | addCpuUsageAlarm: { 49 | Warning: { 50 | maxUsagePercent: 70, 51 | }, 52 | }, 53 | addMinConnectionCountAlarm: { 54 | Warning: { 55 | minConnectionCount: 1, 56 | }, 57 | }, 58 | addMaxConnectionCountAlarm: { 59 | Warning: { 60 | maxConnectionCount: 100, 61 | }, 62 | }, 63 | useCreatedAlarms: { 64 | consume(alarms) { 65 | numAlarmsCreated = alarms.length; 66 | }, 67 | }, 68 | }); 69 | 70 | addMonitoringDashboardsToStack(stack, monitoring); 71 | expect(numAlarmsCreated).toStrictEqual(4); 72 | expect(Template.fromStack(stack)).toMatchSnapshot(); 73 | }); 74 | -------------------------------------------------------------------------------- /test/monitoring/aws-redshift/RedshiftClusterMonitoring.test.ts: -------------------------------------------------------------------------------- 1 | import { Duration, Stack } from "aws-cdk-lib"; 2 | import { Template } from "aws-cdk-lib/assertions"; 3 | 4 | import { RedshiftClusterMonitoring } from "../../../lib"; 5 | import { addMonitoringDashboardsToStack } from "../../utils/SnapshotUtil"; 6 | import { TestMonitoringScope } from "../TestMonitoringScope"; 7 | 8 | test("snapshot test: no alarms", () => { 9 | const stack = new Stack(); 10 | 11 | const scope = new TestMonitoringScope(stack, "Scope"); 12 | 13 | const monitoring = new RedshiftClusterMonitoring(scope, { 14 | alarmFriendlyName: "DummyRedshiftCluster", 15 | clusterIdentifier: "my-redshift-cluster", 16 | }); 17 | 18 | addMonitoringDashboardsToStack(stack, monitoring); 19 | expect(Template.fromStack(stack)).toMatchSnapshot(); 20 | }); 21 | 22 | test("snapshot test: all alarms", () => { 23 | const stack = new Stack(); 24 | 25 | const scope = new TestMonitoringScope(stack, "Scope"); 26 | 27 | let numAlarmsCreated = 0; 28 | 29 | const monitoring = new RedshiftClusterMonitoring(scope, { 30 | clusterIdentifier: "my-redshift-cluster", 31 | addDiskSpaceUsageAlarm: { 32 | Warning: { 33 | maxUsagePercent: 80, 34 | }, 35 | }, 36 | addCpuUsageAlarm: { 37 | Warning: { 38 | maxUsagePercent: 70, 39 | }, 40 | }, 41 | addMinConnectionCountAlarm: { 42 | Warning: { 43 | minConnectionCount: 1, 44 | }, 45 | }, 46 | addMaxConnectionCountAlarm: { 47 | Warning: { 48 | maxConnectionCount: 100, 49 | }, 50 | }, 51 | addMaxLongQueryDurationAlarm: { 52 | Warning: { 53 | maxDuration: Duration.seconds(5), 54 | }, 55 | }, 56 | useCreatedAlarms: { 57 | consume(alarms) { 58 | numAlarmsCreated = alarms.length; 59 | }, 60 | }, 61 | }); 62 | 63 | addMonitoringDashboardsToStack(stack, monitoring); 64 | expect(numAlarmsCreated).toStrictEqual(5); 65 | expect(Template.fromStack(stack)).toMatchSnapshot(); 66 | }); 67 | -------------------------------------------------------------------------------- /test/monitoring/aws-s3/S3BucketMonitoring.test.ts: -------------------------------------------------------------------------------- 1 | import { Stack } from "aws-cdk-lib"; 2 | import { Template } from "aws-cdk-lib/assertions"; 3 | import { Bucket } from "aws-cdk-lib/aws-s3"; 4 | 5 | import { S3BucketMonitoring, StorageType } from "../../../lib"; 6 | import { addMonitoringDashboardsToStack } from "../../utils/SnapshotUtil"; 7 | import { TestMonitoringScope } from "../TestMonitoringScope"; 8 | 9 | test("snapshot test: no alarms", () => { 10 | // GIVEN 11 | const stack = new Stack(); 12 | const bucket = new Bucket(stack, "TestBucket"); 13 | 14 | const scope = new TestMonitoringScope(stack, "Scope"); 15 | 16 | // WHEN 17 | const monitoring = new S3BucketMonitoring(scope, { 18 | bucket, 19 | }); 20 | 21 | // THEN 22 | addMonitoringDashboardsToStack(stack, monitoring); 23 | expect(Template.fromStack(stack)).toMatchSnapshot(); 24 | }); 25 | 26 | test("snapshot test: override StorageType with IntelligentTieringFAStorage", () => { 27 | // GIVEN 28 | const stack = new Stack(); 29 | const bucket = new Bucket(stack, "TestBucket"); 30 | 31 | const scope = new TestMonitoringScope(stack, "Scope"); 32 | 33 | // WHEN 34 | const monitoring = new S3BucketMonitoring(scope, { 35 | bucket: bucket, 36 | storageType: StorageType.INTELLIGENT_TIERING_FA_STORAGE, 37 | }); 38 | 39 | // THEN 40 | addMonitoringDashboardsToStack(stack, monitoring); 41 | expect(Template.fromStack(stack)).toMatchSnapshot(); 42 | }); 43 | -------------------------------------------------------------------------------- /test/monitoring/aws-secretsmanager/SecretsManagerMonitoring.test.ts: -------------------------------------------------------------------------------- 1 | import { Stack } from "aws-cdk-lib"; 2 | import { Template } from "aws-cdk-lib/assertions"; 3 | 4 | import { AlarmWithAnnotation, SecretsManagerMonitoring } from "../../../lib"; 5 | import { addMonitoringDashboardsToStack } from "../../utils/SnapshotUtil"; 6 | import { TestMonitoringScope } from "../TestMonitoringScope"; 7 | 8 | test("snapshot test", () => { 9 | const stack = new Stack(); 10 | 11 | const scope = new TestMonitoringScope(stack, "Scope"); 12 | 13 | const monitoring = new SecretsManagerMonitoring(scope, { 14 | humanReadableName: "SecretsManager", 15 | }); 16 | 17 | addMonitoringDashboardsToStack(stack, monitoring); 18 | expect(Template.fromStack(stack)).toMatchSnapshot(); 19 | }); 20 | 21 | test("snapshot test: all alarms", () => { 22 | const stack = new Stack(); 23 | 24 | const scope = new TestMonitoringScope(stack, "Scope"); 25 | 26 | let numAlarmsCreated = 0; 27 | 28 | const monitoring = new SecretsManagerMonitoring(scope, { 29 | humanReadableName: "SecretsManager", 30 | addChangeInSecretsAlarm: { 31 | Warning: { 32 | alarmWhenDecreased: false, 33 | alarmWhenIncreased: true, 34 | requiredSecretCount: 4, 35 | }, 36 | }, 37 | addMinNumberSecretsAlarm: { 38 | Warning: { 39 | minSecretCount: 3, 40 | }, 41 | }, 42 | addMaxNumberSecretsAlarm: { 43 | Warning: { 44 | maxSecretCount: 5, 45 | }, 46 | }, 47 | useCreatedAlarms: { 48 | consume(alarms: AlarmWithAnnotation[]) { 49 | numAlarmsCreated = alarms.length; 50 | }, 51 | }, 52 | }); 53 | 54 | addMonitoringDashboardsToStack(stack, monitoring); 55 | expect(numAlarmsCreated).toStrictEqual(3); 56 | expect(Template.fromStack(stack)).toMatchSnapshot(); 57 | }); 58 | -------------------------------------------------------------------------------- /test/monitoring/aws-sns/SnsTopicMonitoring.test.ts: -------------------------------------------------------------------------------- 1 | import { Stack } from "aws-cdk-lib"; 2 | import { Template } from "aws-cdk-lib/assertions"; 3 | import { Topic } from "aws-cdk-lib/aws-sns"; 4 | 5 | import { AlarmWithAnnotation, SnsTopicMonitoring } from "../../../lib"; 6 | import { TestMonitoringScope } from "../TestMonitoringScope"; 7 | 8 | test("snapshot test: no alarms", () => { 9 | const stack = new Stack(); 10 | 11 | const scope = new TestMonitoringScope(stack, "Scope"); 12 | 13 | const topic = new Topic(stack, "Topic", { 14 | topicName: "DummyTopic", 15 | }); 16 | 17 | new SnsTopicMonitoring(scope, { 18 | topic, 19 | }); 20 | 21 | // alternative: use reference 22 | 23 | new SnsTopicMonitoring(scope, { 24 | topic: Topic.fromTopicArn( 25 | stack, 26 | "DummyTopicRef", 27 | "arn:aws:sns:us-east-2:123456789012:DummyTopicRef", 28 | ), 29 | }); 30 | 31 | expect(Template.fromStack(stack)).toMatchSnapshot(); 32 | }); 33 | 34 | test("snapshot test: all alarms", () => { 35 | const stack = new Stack(); 36 | 37 | const scope = new TestMonitoringScope(stack, "Scope"); 38 | 39 | const topic = new Topic(stack, "Topic", { 40 | topicName: "DummyTopic", 41 | }); 42 | 43 | let numAlarmsCreated = 0; 44 | 45 | new SnsTopicMonitoring(scope, { 46 | topic, 47 | }); 48 | 49 | new SnsTopicMonitoring(scope, { 50 | topic, 51 | addMessageNotificationsFailedAlarm: { 52 | Warning: { 53 | maxNotificationsFailedCount: 5, 54 | }, 55 | }, 56 | addMinNumberOfMessagesPublishedAlarm: { 57 | Warning: { 58 | minMessagesPublishedCount: 100, 59 | }, 60 | }, 61 | addMaxNumberOfMessagesPublishedAlarm: { 62 | Warning: { 63 | maxMessagesPublishedCount: 200, 64 | }, 65 | }, 66 | useCreatedAlarms: { 67 | consume(alarms: AlarmWithAnnotation[]) { 68 | numAlarmsCreated = alarms.length; 69 | }, 70 | }, 71 | }); 72 | 73 | expect(numAlarmsCreated).toStrictEqual(3); 74 | expect(Template.fromStack(stack)).toMatchSnapshot(); 75 | }); 76 | -------------------------------------------------------------------------------- /test/monitoring/aws-sqs/SqsQueueMonitoring.test.ts: -------------------------------------------------------------------------------- 1 | import { Duration, Stack } from "aws-cdk-lib"; 2 | import { Template } from "aws-cdk-lib/assertions"; 3 | import { Queue } from "aws-cdk-lib/aws-sqs"; 4 | 5 | import { AlarmWithAnnotation, SqsQueueMonitoring } from "../../../lib"; 6 | import { addMonitoringDashboardsToStack } from "../../utils/SnapshotUtil"; 7 | import { TestMonitoringScope } from "../TestMonitoringScope"; 8 | 9 | test("snapshot test: no alarms", () => { 10 | const stack = new Stack(); 11 | 12 | const scope = new TestMonitoringScope(stack, "Scope"); 13 | 14 | const queue = new Queue(stack, "Queue", { 15 | queueName: "DummyQueue", 16 | }); 17 | 18 | const monitoring = new SqsQueueMonitoring(scope, { 19 | queue, 20 | }); 21 | 22 | addMonitoringDashboardsToStack(stack, monitoring); 23 | expect(Template.fromStack(stack)).toMatchSnapshot(); 24 | }); 25 | 26 | test("snapshot test: use fromQueueAttributes, no alarms", () => { 27 | const stack = new Stack(); 28 | 29 | const scope = new TestMonitoringScope(stack, "Scope"); 30 | 31 | new Queue(stack, "Queue", { 32 | queueName: "DummyQueue", 33 | }); 34 | 35 | const monitoring = new SqsQueueMonitoring(scope, { 36 | queue: Queue.fromQueueAttributes(stack, "DummyQueueRef", { 37 | queueArn: "arn:aws:sqs:us-east-2:123456789012:DummyQueueRef", 38 | }), 39 | }); 40 | 41 | addMonitoringDashboardsToStack(stack, monitoring); 42 | expect(Template.fromStack(stack)).toMatchSnapshot(); 43 | }); 44 | 45 | test("snapshot test: all alarms", () => { 46 | const stack = new Stack(); 47 | 48 | const scope = new TestMonitoringScope(stack, "Scope"); 49 | 50 | const queue = new Queue(stack, "Queue", { 51 | queueName: "DummyQueue", 52 | }); 53 | 54 | let numAlarmsCreated = 0; 55 | 56 | const monitoring = new SqsQueueMonitoring(scope, { 57 | queue, 58 | addQueueMinSizeAlarm: { 59 | Warning: { 60 | minMessageCount: 1, 61 | }, 62 | }, 63 | addQueueMaxSizeAlarm: { 64 | Warning: { 65 | maxMessageCount: 5, 66 | }, 67 | }, 68 | addQueueMaxMessageAgeAlarm: { 69 | Warning: { 70 | maxAgeInSeconds: 100, 71 | }, 72 | }, 73 | addQueueMaxTimeToDrainMessagesAlarm: { 74 | Warning: { 75 | maxTimeToDrain: Duration.hours(6), 76 | }, 77 | }, 78 | addQueueMinIncomingMessagesAlarm: { 79 | Warning: { 80 | minIncomingMessagesCount: 0, 81 | }, 82 | }, 83 | addQueueMaxIncomingMessagesAlarm: { 84 | Warning: { 85 | maxIncomingMessagesCount: 1000, 86 | }, 87 | }, 88 | useCreatedAlarms: { 89 | consume(alarms: AlarmWithAnnotation[]) { 90 | numAlarmsCreated = alarms.length; 91 | }, 92 | }, 93 | }); 94 | 95 | addMonitoringDashboardsToStack(stack, monitoring); 96 | expect(numAlarmsCreated).toStrictEqual(6); 97 | expect(Template.fromStack(stack)).toMatchSnapshot(); 98 | }); 99 | -------------------------------------------------------------------------------- /test/monitoring/aws-step-functions/StepFunctionActivityMonitoring.test.ts: -------------------------------------------------------------------------------- 1 | import { Duration, Stack } from "aws-cdk-lib"; 2 | import { Template } from "aws-cdk-lib/assertions"; 3 | import { Activity } from "aws-cdk-lib/aws-stepfunctions"; 4 | 5 | import { 6 | AlarmWithAnnotation, 7 | StepFunctionActivityMonitoring, 8 | } from "../../../lib"; 9 | import { addMonitoringDashboardsToStack } from "../../utils/SnapshotUtil"; 10 | import { TestMonitoringScope } from "../TestMonitoringScope"; 11 | 12 | test("snapshot test: no alarms", () => { 13 | const stack = new Stack(); 14 | 15 | const scope = new TestMonitoringScope(stack, "Scope"); 16 | 17 | const activity = new Activity(stack, "Activity", { 18 | activityName: "DummyActivity", 19 | }); 20 | 21 | const monitoring = new StepFunctionActivityMonitoring(scope, { 22 | alarmFriendlyName: "DummyActivity", 23 | activity, 24 | }); 25 | 26 | addMonitoringDashboardsToStack(stack, monitoring); 27 | expect(Template.fromStack(stack)).toMatchSnapshot(); 28 | }); 29 | 30 | test("snapshot test: all alarms", () => { 31 | const stack = new Stack(); 32 | 33 | const scope = new TestMonitoringScope(stack, "Scope"); 34 | 35 | const activity = new Activity(stack, "Activity", { 36 | activityName: "DummyActivity", 37 | }); 38 | 39 | let numAlarmsCreated = 0; 40 | 41 | const monitoring = new StepFunctionActivityMonitoring(scope, { 42 | alarmFriendlyName: "DummyActivity", 43 | activity, 44 | addDurationP50Alarm: { 45 | Warning: { 46 | maxDuration: Duration.minutes(1), 47 | }, 48 | }, 49 | addDurationP90Alarm: { 50 | Warning: { 51 | maxDuration: Duration.minutes(2), 52 | }, 53 | }, 54 | addDurationP99Alarm: { 55 | Warning: { 56 | maxDuration: Duration.minutes(3), 57 | }, 58 | }, 59 | addFailedActivitiesCountAlarm: { 60 | Warning: { 61 | maxErrorCount: 2, 62 | }, 63 | }, 64 | addFailedActivitiesRateAlarm: { 65 | Warning: { 66 | maxErrorRate: 1, 67 | }, 68 | }, 69 | addTimedOutActivitiesCountAlarm: { 70 | Warning: { 71 | maxErrorCount: 2, 72 | }, 73 | }, 74 | useCreatedAlarms: { 75 | consume(alarms: AlarmWithAnnotation[]) { 76 | numAlarmsCreated = alarms.length; 77 | }, 78 | }, 79 | }); 80 | 81 | addMonitoringDashboardsToStack(stack, monitoring); 82 | expect(numAlarmsCreated).toStrictEqual(6); 83 | expect(Template.fromStack(stack)).toMatchSnapshot(); 84 | }); 85 | -------------------------------------------------------------------------------- /test/monitoring/aws-step-functions/StepFunctionLambdaIntegrationMonitoring.test.ts: -------------------------------------------------------------------------------- 1 | import { Duration, Stack } from "aws-cdk-lib"; 2 | import { Template } from "aws-cdk-lib/assertions"; 3 | import { 4 | determineLatestNodeRuntime, 5 | Function, 6 | InlineCode, 7 | } from "aws-cdk-lib/aws-lambda"; 8 | 9 | import { 10 | AlarmWithAnnotation, 11 | StepFunctionLambdaIntegrationMonitoring, 12 | } from "../../../lib"; 13 | import { addMonitoringDashboardsToStack } from "../../utils/SnapshotUtil"; 14 | import { TestMonitoringScope } from "../TestMonitoringScope"; 15 | 16 | test("snapshot test: no alarms", () => { 17 | const stack = new Stack(); 18 | 19 | const scope = new TestMonitoringScope(stack, "Scope"); 20 | 21 | const lambdaFunction = new Function(stack, "Function", { 22 | functionName: "DummyLambda", 23 | runtime: determineLatestNodeRuntime(stack), 24 | code: InlineCode.fromInline("{}"), 25 | handler: "Dummy::handler", 26 | }); 27 | 28 | const monitoring = new StepFunctionLambdaIntegrationMonitoring(scope, { 29 | alarmFriendlyName: "DummyLambdaIntegration", 30 | lambdaFunction, 31 | }); 32 | 33 | addMonitoringDashboardsToStack(stack, monitoring); 34 | expect(Template.fromStack(stack)).toMatchSnapshot(); 35 | }); 36 | 37 | test("snapshot test: all alarms", () => { 38 | const stack = new Stack(); 39 | 40 | const scope = new TestMonitoringScope(stack, "Scope"); 41 | 42 | const lambdaFunction = new Function(stack, "Function", { 43 | functionName: "DummyLambda", 44 | runtime: determineLatestNodeRuntime(stack), 45 | code: InlineCode.fromInline("{}"), 46 | handler: "Dummy::handler", 47 | }); 48 | 49 | let numAlarmsCreated = 0; 50 | 51 | const monitoring = new StepFunctionLambdaIntegrationMonitoring(scope, { 52 | alarmFriendlyName: "DummyLambdaIntegration", 53 | lambdaFunction, 54 | addDurationP50Alarm: { 55 | Warning: { 56 | maxDuration: Duration.minutes(1), 57 | }, 58 | }, 59 | addDurationP90Alarm: { 60 | Warning: { 61 | maxDuration: Duration.minutes(2), 62 | }, 63 | }, 64 | addDurationP99Alarm: { 65 | Warning: { 66 | maxDuration: Duration.minutes(3), 67 | }, 68 | }, 69 | addFailedFunctionsCountAlarm: { 70 | Warning: { 71 | maxErrorCount: 2, 72 | }, 73 | }, 74 | addFailedFunctionsRateAlarm: { 75 | Warning: { 76 | maxErrorRate: 1, 77 | }, 78 | }, 79 | addTimedOutFunctionsCountAlarm: { 80 | Warning: { 81 | maxErrorCount: 2, 82 | }, 83 | }, 84 | useCreatedAlarms: { 85 | consume(alarms: AlarmWithAnnotation[]) { 86 | numAlarmsCreated = alarms.length; 87 | }, 88 | }, 89 | }); 90 | 91 | addMonitoringDashboardsToStack(stack, monitoring); 92 | expect(numAlarmsCreated).toStrictEqual(6); 93 | expect(Template.fromStack(stack)).toMatchSnapshot(); 94 | }); 95 | -------------------------------------------------------------------------------- /test/monitoring/aws-step-functions/StepFunctionServiceIntegrationMonitoring.test.ts: -------------------------------------------------------------------------------- 1 | import { Duration, Stack } from "aws-cdk-lib"; 2 | import { Template } from "aws-cdk-lib/assertions"; 3 | 4 | import { 5 | AlarmWithAnnotation, 6 | StepFunctionServiceIntegrationMonitoring, 7 | } from "../../../lib"; 8 | import { addMonitoringDashboardsToStack } from "../../utils/SnapshotUtil"; 9 | import { TestMonitoringScope } from "../TestMonitoringScope"; 10 | 11 | test("snapshot test: no alarms", () => { 12 | const stack = new Stack(); 13 | 14 | const scope = new TestMonitoringScope(stack, "Scope"); 15 | 16 | const monitoring = new StepFunctionServiceIntegrationMonitoring(scope, { 17 | alarmFriendlyName: "DummyServiceIntegration", 18 | serviceIntegrationResourceArn: 19 | "arn:aws:states:eu-west-1:216854468193:batch:submitJob.sync", 20 | }); 21 | 22 | addMonitoringDashboardsToStack(stack, monitoring); 23 | expect(Template.fromStack(stack)).toMatchSnapshot(); 24 | }); 25 | 26 | test("snapshot test: all alarms", () => { 27 | const stack = new Stack(); 28 | 29 | const scope = new TestMonitoringScope(stack, "Scope"); 30 | 31 | let numAlarmsCreated = 0; 32 | 33 | const monitoring = new StepFunctionServiceIntegrationMonitoring(scope, { 34 | alarmFriendlyName: "DummyServiceIntegration", 35 | serviceIntegrationResourceArn: 36 | "arn:aws:states:eu-west-1:216854468193:batch:submitJob.sync", 37 | addDurationP50Alarm: { 38 | Warning: { 39 | maxDuration: Duration.minutes(1), 40 | }, 41 | }, 42 | addDurationP90Alarm: { 43 | Warning: { 44 | maxDuration: Duration.minutes(2), 45 | }, 46 | }, 47 | addDurationP99Alarm: { 48 | Warning: { 49 | maxDuration: Duration.minutes(3), 50 | }, 51 | }, 52 | addFailedServiceIntegrationsCountAlarm: { 53 | Warning: { 54 | maxErrorCount: 2, 55 | }, 56 | }, 57 | addFailedServiceIntegrationsRateAlarm: { 58 | Warning: { 59 | maxErrorRate: 1, 60 | }, 61 | }, 62 | addTimedOutServiceIntegrationsCountAlarm: { 63 | Warning: { 64 | maxErrorCount: 2, 65 | }, 66 | }, 67 | useCreatedAlarms: { 68 | consume(alarms: AlarmWithAnnotation[]) { 69 | numAlarmsCreated = alarms.length; 70 | }, 71 | }, 72 | }); 73 | 74 | addMonitoringDashboardsToStack(stack, monitoring); 75 | expect(numAlarmsCreated).toStrictEqual(6); 76 | expect(Template.fromStack(stack)).toMatchSnapshot(); 77 | }); 78 | -------------------------------------------------------------------------------- /test/monitoring/aws-synthetics/SyntheticsCanaryMonitoring.test.ts: -------------------------------------------------------------------------------- 1 | import { Duration, Stack } from "aws-cdk-lib"; 2 | import { Template } from "aws-cdk-lib/assertions"; 3 | import { 4 | Canary, 5 | Code, 6 | Runtime, 7 | Schedule, 8 | Test, 9 | } from "aws-cdk-lib/aws-synthetics"; 10 | 11 | import { AlarmWithAnnotation } from "../../../lib"; 12 | import { SyntheticsCanaryMonitoring } from "../../../lib/monitoring/aws-synthetics"; 13 | import { addMonitoringDashboardsToStack } from "../../utils/SnapshotUtil"; 14 | import { TestMonitoringScope } from "../TestMonitoringScope"; 15 | 16 | test("snapshot test: no alarms", () => { 17 | const stack = new Stack(); 18 | const canary = new Canary(stack, "Canary", { 19 | schedule: Schedule.rate(Duration.minutes(5)), 20 | test: Test.custom({ 21 | code: Code.fromInline("/* nothing */"), 22 | handler: "index.handler", // must end with '.handler' 23 | }), 24 | runtime: Runtime.SYNTHETICS_NODEJS_PUPPETEER_5_1, 25 | }); 26 | 27 | const scope = new TestMonitoringScope(stack, "Scope"); 28 | 29 | const monitoring = new SyntheticsCanaryMonitoring(scope, { canary }); 30 | 31 | addMonitoringDashboardsToStack(stack, monitoring); 32 | expect(Template.fromStack(stack)).toMatchSnapshot(); 33 | }); 34 | 35 | test("snapshot test: all alarms", () => { 36 | const stack = new Stack(); 37 | const canary = new Canary(stack, "Canary", { 38 | schedule: Schedule.rate(Duration.minutes(5)), 39 | test: Test.custom({ 40 | code: Code.fromInline("/* nothing */"), 41 | handler: "index.handler", // must end with '.handler' 42 | }), 43 | runtime: Runtime.SYNTHETICS_NODEJS_PUPPETEER_5_1, 44 | }); 45 | 46 | const scope = new TestMonitoringScope(stack, "Scope"); 47 | 48 | let numAlarmsCreated = 0; 49 | 50 | const monitoring = new SyntheticsCanaryMonitoring(scope, { 51 | canary, 52 | addAverageLatencyAlarm: { 53 | Warning: { 54 | maxLatency: Duration.seconds(10), 55 | }, 56 | }, 57 | add4xxErrorCountAlarm: { 58 | Warning: { 59 | maxErrorCount: 1, 60 | }, 61 | }, 62 | add5xxFaultCountAlarm: { 63 | Warning: { 64 | maxErrorCount: 2, 65 | }, 66 | }, 67 | add4xxErrorRateAlarm: { 68 | Warning: { 69 | maxErrorRate: 0.5, 70 | }, 71 | }, 72 | add5xxFaultRateAlarm: { 73 | Warning: { 74 | maxErrorRate: 0.8, 75 | }, 76 | }, 77 | useCreatedAlarms: { 78 | consume(alarms: AlarmWithAnnotation[]) { 79 | numAlarmsCreated = alarms.length; 80 | }, 81 | }, 82 | }); 83 | 84 | expect(numAlarmsCreated).toStrictEqual(5); 85 | addMonitoringDashboardsToStack(stack, monitoring); 86 | expect(Template.fromStack(stack)).toMatchSnapshot(); 87 | }); 88 | -------------------------------------------------------------------------------- /test/monitoring/aws-wafv2/WafV2Monitoring.test.ts: -------------------------------------------------------------------------------- 1 | import { Stack } from "aws-cdk-lib"; 2 | import { Template } from "aws-cdk-lib/assertions"; 3 | import { CfnWebACL } from "aws-cdk-lib/aws-wafv2"; 4 | 5 | import { WafV2Monitoring } from "../../../lib"; 6 | import { addMonitoringDashboardsToStack } from "../../utils/SnapshotUtil"; 7 | import { TestMonitoringScope } from "../TestMonitoringScope"; 8 | 9 | test("snapshot test: no alarms", () => { 10 | const stack = new Stack(); 11 | const acl = new CfnWebACL(stack, "DummyAcl", { 12 | name: "DummyAclName", 13 | defaultAction: { allow: {} }, 14 | scope: "REGIONAL", 15 | visibilityConfig: { 16 | sampledRequestsEnabled: true, 17 | cloudWatchMetricsEnabled: true, 18 | metricName: "DummyMetricName", 19 | }, 20 | }); 21 | 22 | const scope = new TestMonitoringScope(stack, "Scope"); 23 | 24 | const monitoring = new WafV2Monitoring(scope, { acl, region: "us-east-1" }); 25 | 26 | addMonitoringDashboardsToStack(stack, monitoring); 27 | expect(Template.fromStack(stack)).toMatchSnapshot(); 28 | }); 29 | 30 | test("with REGIONAL ACL but no region prop, throws error", () => { 31 | const stack = new Stack(); 32 | const acl = new CfnWebACL(stack, "DummyAcl", { 33 | name: "DummyAclName", 34 | defaultAction: { allow: {} }, 35 | scope: "REGIONAL", 36 | visibilityConfig: { 37 | sampledRequestsEnabled: true, 38 | cloudWatchMetricsEnabled: true, 39 | metricName: "DummyMetricName", 40 | }, 41 | }); 42 | 43 | const scope = new TestMonitoringScope(stack, "Scope"); 44 | 45 | expect(() => new WafV2Monitoring(scope, { acl })).toThrow( 46 | `region is required if CfnWebACL has "REGIONAL" scope`, 47 | ); 48 | }); 49 | 50 | test("snapshot test: all alarms", () => { 51 | const stack = new Stack(); 52 | const acl = new CfnWebACL(stack, "DummyAcl", { 53 | name: "DummyAclName", 54 | defaultAction: { allow: {} }, 55 | scope: "CLOUDFRONT", 56 | visibilityConfig: { 57 | sampledRequestsEnabled: true, 58 | cloudWatchMetricsEnabled: true, 59 | metricName: "DummyMetricName", 60 | }, 61 | }); 62 | 63 | const scope = new TestMonitoringScope(stack, "Scope"); 64 | 65 | let numAlarmsCreated = 0; 66 | 67 | const monitoring = new WafV2Monitoring(scope, { 68 | acl, 69 | addBlockedRequestsCountAlarm: { 70 | Warning: { 71 | maxErrorCount: 5, 72 | }, 73 | }, 74 | addBlockedRequestsRateAlarm: { 75 | Warning: { 76 | maxErrorRate: 0.05, 77 | }, 78 | }, 79 | useCreatedAlarms: { 80 | consume(alarms) { 81 | numAlarmsCreated = alarms.length; 82 | }, 83 | }, 84 | }); 85 | 86 | addMonitoringDashboardsToStack(stack, monitoring); 87 | expect(numAlarmsCreated).toStrictEqual(2); 88 | expect(Template.fromStack(stack)).toMatchSnapshot(); 89 | }); 90 | -------------------------------------------------------------------------------- /test/monitoring/fluentbit/FluentBitMonitoring.test.ts: -------------------------------------------------------------------------------- 1 | import { Stack } from "aws-cdk-lib"; 2 | import { Template } from "aws-cdk-lib/assertions"; 3 | import { LogGroup } from "aws-cdk-lib/aws-logs"; 4 | import { FluentBitMonitoring } from "../../../lib"; 5 | import { addMonitoringDashboardsToStack } from "../../utils/SnapshotUtil"; 6 | import { TestMonitoringScope } from "../TestMonitoringScope"; 7 | 8 | test("snapshot test without all filters", () => { 9 | const stack = new Stack(); 10 | const scope = new TestMonitoringScope(stack, "Scope"); 11 | const logGroup = new LogGroup(stack, "DummyLogGroup"); 12 | const monitoring = new FluentBitMonitoring(scope, { 13 | logGroup, 14 | namespace: "DummyNamespace", 15 | }); 16 | 17 | addMonitoringDashboardsToStack(stack, monitoring); 18 | expect(Template.fromStack(stack)).toMatchSnapshot(); 19 | }); 20 | 21 | test("snapshot test with all filters", () => { 22 | const stack = new Stack(); 23 | const scope = new TestMonitoringScope(stack, "Scope"); 24 | const logGroup = new LogGroup(stack, "DummyLogGroup"); 25 | const monitoring = new FluentBitMonitoring(scope, { 26 | logGroup, 27 | namespace: "DummyNamespace", 28 | createOptionalMetricFilters: true, 29 | }); 30 | 31 | addMonitoringDashboardsToStack(stack, monitoring); 32 | expect(Template.fromStack(stack)).toMatchSnapshot(); 33 | }); 34 | -------------------------------------------------------------------------------- /test/setup/setup.ts: -------------------------------------------------------------------------------- 1 | // Helps ensure consistent test snapshot generation between runs 2 | expect.addSnapshotSerializer({ 3 | test: (val) => typeof val === "string" && /([A-Fa-f0-9]{64}).zip/.test(val), 4 | print: (val) => 5 | `"${(val as string).replace( 6 | /([A-Fa-f0-9]{64}).zip/, 7 | "[HASH REMOVED].zip", 8 | )}"`, 9 | }); 10 | 11 | expect.addSnapshotSerializer({ 12 | test: (val) => 13 | val && Object.prototype.hasOwnProperty.call(val, "LatestNodeRuntimeMap"), 14 | print: (_val) => "[MAPPING REMOVED]", 15 | }); 16 | -------------------------------------------------------------------------------- /test/utils/SnapshotUtil.ts: -------------------------------------------------------------------------------- 1 | import { Stack } from "aws-cdk-lib"; 2 | import { Dashboard } from "aws-cdk-lib/aws-cloudwatch"; 3 | import { Monitoring } from "../../lib"; 4 | 5 | /** 6 | * Executes a snapshot test for widgets, summary widgets and alarm widgets. 7 | * 8 | * @param monitoring monitoring to test 9 | */ 10 | export function addMonitoringDashboardsToStack( 11 | stack: Stack, 12 | monitoring: Monitoring, 13 | ) { 14 | const summaryDashboard = new Dashboard(stack, "Summary"); 15 | summaryDashboard.addWidgets(...monitoring.summaryWidgets()); 16 | const alarmDashboard = new Dashboard(stack, "Alarm"); 17 | alarmDashboard.addWidgets(...monitoring.alarmWidgets()); 18 | const dashboard = new Dashboard(stack, "Default"); 19 | dashboard.addWidgets(...monitoring.widgets()); 20 | } 21 | -------------------------------------------------------------------------------- /tsconfig.dev.json: -------------------------------------------------------------------------------- 1 | // ~~ Generated by projen. To modify, edit .projenrc.ts and run "npx projen". 2 | { 3 | "compilerOptions": { 4 | "alwaysStrict": true, 5 | "declaration": true, 6 | "esModuleInterop": true, 7 | "experimentalDecorators": true, 8 | "inlineSourceMap": true, 9 | "inlineSources": true, 10 | "lib": [ 11 | "es2019" 12 | ], 13 | "module": "CommonJS", 14 | "noEmitOnError": false, 15 | "noFallthroughCasesInSwitch": true, 16 | "noImplicitAny": true, 17 | "noImplicitReturns": true, 18 | "noImplicitThis": true, 19 | "noUnusedLocals": true, 20 | "noUnusedParameters": true, 21 | "resolveJsonModule": true, 22 | "strict": true, 23 | "strictNullChecks": true, 24 | "strictPropertyInitialization": true, 25 | "stripInternal": true, 26 | "target": "ES2019" 27 | }, 28 | "include": [ 29 | "lib/**/*.ts", 30 | "test/**/*.ts", 31 | ".projenrc.ts", 32 | "projenrc/**/*.ts" 33 | ], 34 | "exclude": [ 35 | "node_modules" 36 | ] 37 | } 38 | --------------------------------------------------------------------------------