From 49a67aa4933987d99425a1dd366efc5eb5079a14 Mon Sep 17 00:00:00 2001 From: aki-kii Date: Wed, 19 Nov 2025 02:37:35 +0900 Subject: [PATCH 1/9] feat(cloudwatch): imprements AT_LEAST wrapper function of comopsite alarm --- ...siteAlarmImportIntegrationTest.assets.json | 5 +- .../CompositeAlarmIntegrationTest.assets.json | 9 +- ...ompositeAlarmIntegrationTest.template.json | 66 +- .../integ.composite-alarm.js.snapshot/cdk.out | 2 +- ...efaultTestDeployAssertF4EF969E.assets.json | 5 +- .../integ.json | 5 +- .../manifest.json | 580 +++++++++++++++++- .../tree.json | 378 +----------- .../test/integ.composite-alarm.ts | 8 + packages/aws-cdk-lib/aws-cloudwatch/README.md | 4 + .../aws-cloudwatch/lib/alarm-rule.ts | 118 ++++ .../test/composite-alarm.test.ts | 85 ++- 12 files changed, 861 insertions(+), 404 deletions(-) diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-cloudwatch/test/integ.composite-alarm.js.snapshot/CompositeAlarmImportIntegrationTest.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-cloudwatch/test/integ.composite-alarm.js.snapshot/CompositeAlarmImportIntegrationTest.assets.json index 4801cb2cefc9d..0a509d2cfcb5e 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-cloudwatch/test/integ.composite-alarm.js.snapshot/CompositeAlarmImportIntegrationTest.assets.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-cloudwatch/test/integ.composite-alarm.js.snapshot/CompositeAlarmImportIntegrationTest.assets.json @@ -1,13 +1,14 @@ { - "version": "31.0.0", + "version": "48.0.0", "files": { "a29bbb21cb63cde6a2d31ed4212e73dab8bfd786cf26aab0156b74490595c0a6": { + "displayName": "CompositeAlarmImportIntegrationTest Template", "source": { "path": "CompositeAlarmImportIntegrationTest.template.json", "packaging": "file" }, "destinations": { - "current_account-current_region": { + "current_account-current_region-972e9596": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", "objectKey": "a29bbb21cb63cde6a2d31ed4212e73dab8bfd786cf26aab0156b74490595c0a6.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-cloudwatch/test/integ.composite-alarm.js.snapshot/CompositeAlarmIntegrationTest.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-cloudwatch/test/integ.composite-alarm.js.snapshot/CompositeAlarmIntegrationTest.assets.json index 94febd1e90497..234931b692c6b 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-cloudwatch/test/integ.composite-alarm.js.snapshot/CompositeAlarmIntegrationTest.assets.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-cloudwatch/test/integ.composite-alarm.js.snapshot/CompositeAlarmIntegrationTest.assets.json @@ -1,15 +1,16 @@ { - "version": "31.0.0", + "version": "48.0.0", "files": { - "de5a3d0e66f4d4eab13b64e32203fdcae3b36670c889c72a3b4b752e6a5d0fc2": { + "3943b179d9074a1006b8024b5a71901e57466caf9c2a157b6a20fd75f1faa1fd": { + "displayName": "CompositeAlarmIntegrationTest Template", "source": { "path": "CompositeAlarmIntegrationTest.template.json", "packaging": "file" }, "destinations": { - "current_account-current_region": { + "current_account-current_region-77901b19": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "de5a3d0e66f4d4eab13b64e32203fdcae3b36670c889c72a3b4b752e6a5d0fc2.json", + "objectKey": "3943b179d9074a1006b8024b5a71901e57466caf9c2a157b6a20fd75f1faa1fd.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-cloudwatch/test/integ.composite-alarm.js.snapshot/CompositeAlarmIntegrationTest.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-cloudwatch/test/integ.composite-alarm.js.snapshot/CompositeAlarmIntegrationTest.template.json index e3e92708c65a9..044aafebe9f44 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-cloudwatch/test/integ.composite-alarm.js.snapshot/CompositeAlarmIntegrationTest.template.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-cloudwatch/test/integ.composite-alarm.js.snapshot/CompositeAlarmIntegrationTest.template.json @@ -51,9 +51,9 @@ "Alarm548383B2F": { "Type": "AWS::CloudWatch::Alarm", "Properties": { + "AlarmName": "Alarm with space in name", "ComparisonOperator": "GreaterThanOrEqualToThreshold", "EvaluationPeriods": 3, - "AlarmName": "Alarm with space in name", "MetricName": "Metric", "Namespace": "CDK/Test", "Period": 300, @@ -64,6 +64,15 @@ "CompositeAlarmF4C3D082": { "Type": "AWS::CloudWatch::CompositeAlarm", "Properties": { + "ActionsSuppressor": { + "Fn::GetAtt": [ + "Alarm548383B2F", + "Arn" + ] + }, + "ActionsSuppressorExtensionPeriod": 60, + "ActionsSuppressorWaitPeriod": 60, + "AlarmName": "CompositeAlarmIntegrationTestCompositeAlarm742D2FBA", "AlarmRule": { "Fn::Join": [ "", @@ -103,19 +112,52 @@ "Arn" ] }, - "\")))) OR FALSE)" + "\"))) AND AT_LEAST(2, ALARM , (", + { + "Fn::GetAtt": [ + "Alarm1F9009D71", + "Arn" + ] + }, + ", ", + { + "Fn::GetAtt": [ + "Alarm2A7122E13", + "Arn" + ] + }, + ", ", + { + "Fn::GetAtt": [ + "Alarm32341D8D9", + "Arn" + ] + }, + ")) AND AT_LEAST(60%, NOT OK , (", + { + "Fn::GetAtt": [ + "Alarm1F9009D71", + "Arn" + ] + }, + ", ", + { + "Fn::GetAtt": [ + "Alarm2A7122E13", + "Arn" + ] + }, + ", ", + { + "Fn::GetAtt": [ + "Alarm32341D8D9", + "Arn" + ] + }, + "))) OR FALSE)" ] ] - }, - "ActionsSuppressor": { - "Fn::GetAtt": [ - "Alarm548383B2F", - "Arn" - ] - }, - "ActionsSuppressorExtensionPeriod": 60, - "ActionsSuppressorWaitPeriod": 60, - "AlarmName": "CompositeAlarmIntegrationTestCompositeAlarm742D2FBA" + } } } }, diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-cloudwatch/test/integ.composite-alarm.js.snapshot/cdk.out b/packages/@aws-cdk-testing/framework-integ/test/aws-cloudwatch/test/integ.composite-alarm.js.snapshot/cdk.out index 7925065efbcc4..523a9aac37cbf 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-cloudwatch/test/integ.composite-alarm.js.snapshot/cdk.out +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-cloudwatch/test/integ.composite-alarm.js.snapshot/cdk.out @@ -1 +1 @@ -{"version":"31.0.0"} \ No newline at end of file +{"version":"48.0.0"} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-cloudwatch/test/integ.composite-alarm.js.snapshot/cdkintegcompositealarmDefaultTestDeployAssertF4EF969E.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-cloudwatch/test/integ.composite-alarm.js.snapshot/cdkintegcompositealarmDefaultTestDeployAssertF4EF969E.assets.json index 7cbfec60588f0..6b22cb2940d99 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-cloudwatch/test/integ.composite-alarm.js.snapshot/cdkintegcompositealarmDefaultTestDeployAssertF4EF969E.assets.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-cloudwatch/test/integ.composite-alarm.js.snapshot/cdkintegcompositealarmDefaultTestDeployAssertF4EF969E.assets.json @@ -1,13 +1,14 @@ { - "version": "31.0.0", + "version": "48.0.0", "files": { "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22": { + "displayName": "cdkintegcompositealarmDefaultTestDeployAssertF4EF969E Template", "source": { "path": "cdkintegcompositealarmDefaultTestDeployAssertF4EF969E.template.json", "packaging": "file" }, "destinations": { - "current_account-current_region": { + "current_account-current_region-d8d86b35": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", "objectKey": "21fbb51d7b23f6a6c262b46a9caee79d744a3ac019fd45422d988b96d44b2a22.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-cloudwatch/test/integ.composite-alarm.js.snapshot/integ.json b/packages/@aws-cdk-testing/framework-integ/test/aws-cloudwatch/test/integ.composite-alarm.js.snapshot/integ.json index b56d7d84db9f6..a74e29f1201af 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-cloudwatch/test/integ.composite-alarm.js.snapshot/integ.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-cloudwatch/test/integ.composite-alarm.js.snapshot/integ.json @@ -1,5 +1,5 @@ { - "version": "31.0.0", + "version": "48.0.0", "testCases": { "cdk-integ-composite-alarm/DefaultTest": { "stacks": [ @@ -9,5 +9,6 @@ "assertionStack": "cdk-integ-composite-alarm/DefaultTest/DeployAssert", "assertionStackName": "cdkintegcompositealarmDefaultTestDeployAssertF4EF969E" } - } + }, + "minimumCliVersion": "2.1027.0" } \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-cloudwatch/test/integ.composite-alarm.js.snapshot/manifest.json b/packages/@aws-cdk-testing/framework-integ/test/aws-cloudwatch/test/integ.composite-alarm.js.snapshot/manifest.json index 3872b65b9b43a..ce7fc2ef3a343 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-cloudwatch/test/integ.composite-alarm.js.snapshot/manifest.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-cloudwatch/test/integ.composite-alarm.js.snapshot/manifest.json @@ -1,5 +1,5 @@ { - "version": "31.0.0", + "version": "48.0.0", "artifacts": { "CompositeAlarmIntegrationTest.assets": { "type": "cdk:asset-manifest", @@ -14,10 +14,11 @@ "environment": "aws://unknown-account/unknown-region", "properties": { "templateFile": "CompositeAlarmIntegrationTest.template.json", + "terminationProtection": false, "validateOnSynth": false, "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", - "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/de5a3d0e66f4d4eab13b64e32203fdcae3b36670c889c72a3b4b752e6a5d0fc2.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/3943b179d9074a1006b8024b5a71901e57466caf9c2a157b6a20fd75f1faa1fd.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ @@ -33,36 +34,111 @@ "CompositeAlarmIntegrationTest.assets" ], "metadata": { + "/CompositeAlarmIntegrationTest/Alarm1": [ + { + "type": "aws:cdk:analytics:construct", + "data": { + "metric": { + "warnings": "*", + "warningsV2": "*" + }, + "threshold": "*", + "evaluationPeriods": "*" + } + } + ], "/CompositeAlarmIntegrationTest/Alarm1/Resource": [ { "type": "aws:cdk:logicalId", "data": "Alarm1F9009D71" } ], + "/CompositeAlarmIntegrationTest/Alarm2": [ + { + "type": "aws:cdk:analytics:construct", + "data": { + "metric": { + "warnings": "*", + "warningsV2": "*" + }, + "threshold": "*", + "evaluationPeriods": "*" + } + } + ], "/CompositeAlarmIntegrationTest/Alarm2/Resource": [ { "type": "aws:cdk:logicalId", "data": "Alarm2A7122E13" } ], + "/CompositeAlarmIntegrationTest/Alarm3": [ + { + "type": "aws:cdk:analytics:construct", + "data": { + "metric": { + "warnings": "*", + "warningsV2": "*" + }, + "threshold": "*", + "evaluationPeriods": "*" + } + } + ], "/CompositeAlarmIntegrationTest/Alarm3/Resource": [ { "type": "aws:cdk:logicalId", "data": "Alarm32341D8D9" } ], + "/CompositeAlarmIntegrationTest/Alarm4": [ + { + "type": "aws:cdk:analytics:construct", + "data": { + "metric": { + "warnings": "*", + "warningsV2": "*" + }, + "threshold": "*", + "evaluationPeriods": "*" + } + } + ], "/CompositeAlarmIntegrationTest/Alarm4/Resource": [ { "type": "aws:cdk:logicalId", "data": "Alarm4671832C8" } ], + "/CompositeAlarmIntegrationTest/Alarm5": [ + { + "type": "aws:cdk:analytics:construct", + "data": { + "alarmName": "*", + "metric": { + "warnings": "*", + "warningsV2": "*" + }, + "threshold": "*", + "evaluationPeriods": "*" + } + } + ], "/CompositeAlarmIntegrationTest/Alarm5/Resource": [ { "type": "aws:cdk:logicalId", "data": "Alarm548383B2F" } ], + "/CompositeAlarmIntegrationTest/CompositeAlarm": [ + { + "type": "aws:cdk:analytics:construct", + "data": { + "alarmRule": "*", + "actionsSuppressor": "*" + } + } + ], "/CompositeAlarmIntegrationTest/CompositeAlarm/Resource": [ { "type": "aws:cdk:logicalId", @@ -97,6 +173,7 @@ "environment": "aws://unknown-account/unknown-region", "properties": { "templateFile": "CompositeAlarmImportIntegrationTest.template.json", + "terminationProtection": false, "validateOnSynth": false, "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", @@ -156,6 +233,7 @@ "environment": "aws://unknown-account/unknown-region", "properties": { "templateFile": "cdkintegcompositealarmDefaultTestDeployAssertF4EF969E.template.json", + "terminationProtection": false, "validateOnSynth": false, "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", @@ -195,6 +273,502 @@ "properties": { "file": "tree.json" } + }, + "aws-cdk-lib/feature-flag-report": { + "type": "cdk:feature-flag-report", + "properties": { + "module": "aws-cdk-lib", + "flags": { + "@aws-cdk/aws-signer:signingProfileNamePassedToCfn": { + "userValue": true, + "recommendedValue": true, + "explanation": "Pass signingProfileName to CfnSigningProfile" + }, + "@aws-cdk/core:newStyleStackSynthesis": { + "recommendedValue": true, + "explanation": "Switch to new stack synthesis method which enables CI/CD", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/core:stackRelativeExports": { + "recommendedValue": true, + "explanation": "Name exports based on the construct paths relative to the stack, rather than the global construct path", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/aws-ecs-patterns:secGroupsDisablesImplicitOpenListener": { + "userValue": true, + "recommendedValue": true, + "explanation": "Disable implicit openListener when custom security groups are provided" + }, + "@aws-cdk/aws-rds:lowercaseDbIdentifier": { + "recommendedValue": true, + "explanation": "Force lowercasing of RDS Cluster names in CDK", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": { + "recommendedValue": true, + "explanation": "Allow adding/removing multiple UsagePlanKeys independently", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/aws-lambda:recognizeVersionProps": { + "recommendedValue": true, + "explanation": "Enable this feature flag to opt in to the updated logical id calculation for Lambda Version created using the `fn.currentVersion`.", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/aws-lambda:recognizeLayerVersion": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enable this feature flag to opt in to the updated logical id calculation for Lambda Version created using the `fn.currentVersion`." + }, + "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": { + "recommendedValue": true, + "explanation": "Enable this feature flag to have cloudfront distributions use the security policy TLSv1.2_2021 by default.", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/core:checkSecretUsage": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enable this flag to make it impossible to accidentally use SecretValues in unsafe locations" + }, + "@aws-cdk/core:target-partitions": { + "recommendedValue": [ + "aws", + "aws-cn" + ], + "explanation": "What regions to include in lookup tables of environment agnostic stacks" + }, + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": { + "userValue": true, + "recommendedValue": true, + "explanation": "ECS extensions will automatically add an `awslogs` driver if no logging is specified" + }, + "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enable this feature flag to have Launch Templates generated by the `InstanceRequireImdsv2Aspect` use unique names." + }, + "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": { + "userValue": true, + "recommendedValue": true, + "explanation": "ARN format used by ECS. In the new ARN format, the cluster name is part of the resource ID." + }, + "@aws-cdk/aws-iam:minimizePolicies": { + "userValue": true, + "recommendedValue": true, + "explanation": "Minimize IAM policies by combining Statements" + }, + "@aws-cdk/core:validateSnapshotRemovalPolicy": { + "userValue": true, + "recommendedValue": true, + "explanation": "Error on snapshot removal policies on resources that do not support it." + }, + "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": { + "userValue": true, + "recommendedValue": true, + "explanation": "Generate key aliases that include the stack name" + }, + "@aws-cdk/aws-s3:createDefaultLoggingPolicy": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enable this feature flag to create an S3 bucket policy by default in cases where an AWS service would automatically create the Policy if one does not exist." + }, + "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": { + "userValue": true, + "recommendedValue": true, + "explanation": "Restrict KMS key policy for encrypted Queues a bit more" + }, + "@aws-cdk/aws-apigateway:disableCloudWatchRole": { + "userValue": true, + "recommendedValue": true, + "explanation": "Make default CloudWatch Role behavior safe for multiple API Gateways in one environment" + }, + "@aws-cdk/core:enablePartitionLiterals": { + "userValue": true, + "recommendedValue": true, + "explanation": "Make ARNs concrete if AWS partition is known" + }, + "@aws-cdk/aws-events:eventsTargetQueueSameAccount": { + "userValue": true, + "recommendedValue": true, + "explanation": "Event Rules may only push to encrypted SQS queues in the same account" + }, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": { + "userValue": true, + "recommendedValue": true, + "explanation": "Avoid setting the \"ECS\" deployment controller when adding a circuit breaker" + }, + "@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enable this feature to create default policy names for imported roles that depend on the stack the role is in." + }, + "@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": { + "userValue": true, + "recommendedValue": true, + "explanation": "Use S3 Bucket Policy instead of ACLs for Server Access Logging" + }, + "@aws-cdk/aws-route53-patters:useCertificate": { + "userValue": true, + "recommendedValue": true, + "explanation": "Use the official `Certificate` resource instead of `DnsValidatedCertificate`" + }, + "@aws-cdk/customresources:installLatestAwsSdkDefault": { + "userValue": false, + "recommendedValue": false, + "explanation": "Whether to install the latest SDK by default in AwsCustomResource" + }, + "@aws-cdk/aws-rds:databaseProxyUniqueResourceName": { + "userValue": true, + "recommendedValue": true, + "explanation": "Use unique resource name for Database Proxy" + }, + "@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": { + "userValue": true, + "recommendedValue": true, + "explanation": "Remove CloudWatch alarms from deployment group" + }, + "@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": { + "userValue": true, + "recommendedValue": true, + "explanation": "Include authorizer configuration in the calculation of the API deployment logical ID." + }, + "@aws-cdk/aws-ec2:launchTemplateDefaultUserData": { + "userValue": true, + "recommendedValue": true, + "explanation": "Define user data for a launch template by default when a machine image is provided." + }, + "@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": { + "userValue": true, + "recommendedValue": true, + "explanation": "SecretTargetAttachments uses the ResourcePolicy of the attached Secret." + }, + "@aws-cdk/aws-redshift:columnId": { + "userValue": true, + "recommendedValue": true, + "explanation": "Whether to use an ID to track Redshift column changes" + }, + "@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enable AmazonEMRServicePolicy_v2 managed policies" + }, + "@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": { + "userValue": true, + "recommendedValue": true, + "explanation": "Restrict access to the VPC default security group" + }, + "@aws-cdk/aws-apigateway:requestValidatorUniqueId": { + "userValue": true, + "recommendedValue": true, + "explanation": "Generate a unique id for each RequestValidator added to a method" + }, + "@aws-cdk/aws-kms:aliasNameRef": { + "userValue": true, + "recommendedValue": true, + "explanation": "KMS Alias name and keyArn will have implicit reference to KMS Key" + }, + "@aws-cdk/aws-kms:applyImportedAliasPermissionsToPrincipal": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enable grant methods on Aliases imported by name to use kms:ResourceAliases condition" + }, + "@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": { + "userValue": true, + "recommendedValue": true, + "explanation": "Generate a launch template when creating an AutoScalingGroup" + }, + "@aws-cdk/core:includePrefixInUniqueNameGeneration": { + "userValue": true, + "recommendedValue": true, + "explanation": "Include the stack prefix in the stack name generation process" + }, + "@aws-cdk/aws-efs:denyAnonymousAccess": { + "userValue": true, + "recommendedValue": true, + "explanation": "EFS denies anonymous clients accesses" + }, + "@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enables support for Multi-AZ with Standby deployment for opensearch domains" + }, + "@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enables aws-lambda-nodejs.Function to use the latest available NodeJs runtime as the default" + }, + "@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, mount targets will have a stable logicalId that is linked to the associated subnet." + }, + "@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, a scope of InstanceParameterGroup for AuroraClusterInstance with each parameters will change." + }, + "@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, will always use the arn for identifiers for CfnSourceApiAssociation in the GraphqlApi construct rather than id." + }, + "@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, creating an RDS database cluster from a snapshot will only render credentials for snapshot credentials." + }, + "@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the CodeCommit source action is using the default branch name 'main'." + }, + "@aws-cdk/aws-cloudwatch-actions:changeLambdaPermissionLogicalIdForLambdaAction": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the logical ID of a Lambda permission for a Lambda action includes an alarm ID." + }, + "@aws-cdk/aws-codepipeline:crossAccountKeysDefaultValueToFalse": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enables Pipeline to set the default value for crossAccountKeys to false." + }, + "@aws-cdk/aws-codepipeline:defaultPipelineTypeToV2": { + "userValue": true, + "recommendedValue": true, + "explanation": "Enables Pipeline to set the default pipeline type to V2." + }, + "@aws-cdk/aws-kms:reduceCrossAccountRegionPolicyScope": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, IAM Policy created from KMS key grant will reduce the resource scope to this key only." + }, + "@aws-cdk/pipelines:reduceAssetRoleTrustScope": { + "recommendedValue": true, + "explanation": "Remove the root account principal from PipelineAssetsFileRole trust policy", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/aws-eks:nodegroupNameAttribute": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, nodegroupName attribute of the provisioned EKS NodeGroup will not have the cluster name prefix." + }, + "@aws-cdk/aws-ec2:ebsDefaultGp3Volume": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the default volume type of the EBS volume will be GP3" + }, + "@aws-cdk/aws-ecs:removeDefaultDeploymentAlarm": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, remove default deployment alarm settings" + }, + "@aws-cdk/custom-resources:logApiResponseDataPropertyTrueDefault": { + "userValue": false, + "recommendedValue": false, + "explanation": "When enabled, the custom resource used for `AwsCustomResource` will configure the `logApiResponseData` property as true by default" + }, + "@aws-cdk/aws-s3:keepNotificationInImportedBucket": { + "userValue": false, + "recommendedValue": false, + "explanation": "When enabled, Adding notifications to a bucket in the current stack will not remove notification from imported stack." + }, + "@aws-cdk/aws-stepfunctions-tasks:useNewS3UriParametersForBedrockInvokeModelTask": { + "recommendedValue": true, + "explanation": "When enabled, use new props for S3 URI field in task definition of state machine for bedrock invoke model.", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/core:explicitStackTags": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, stack tags need to be assigned explicitly on a Stack." + }, + "@aws-cdk/aws-ecs:enableImdsBlockingDeprecatedFeature": { + "userValue": false, + "recommendedValue": false, + "explanation": "When set to true along with canContainersAccessInstanceRole=false in ECS cluster, new updated commands will be added to UserData to block container accessing IMDS. **Applicable to Linux only. IMPORTANT: See [details.](#aws-cdkaws-ecsenableImdsBlockingDeprecatedFeature)**" + }, + "@aws-cdk/aws-ecs:disableEcsImdsBlocking": { + "userValue": true, + "recommendedValue": true, + "explanation": "When set to true, CDK synth will throw exception if canContainersAccessInstanceRole is false. **IMPORTANT: See [details.](#aws-cdkaws-ecsdisableEcsImdsBlocking)**" + }, + "@aws-cdk/aws-ecs:reduceEc2FargateCloudWatchPermissions": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, we will only grant the necessary permissions when users specify cloudwatch log group through logConfiguration" + }, + "@aws-cdk/aws-dynamodb:resourcePolicyPerReplica": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled will allow you to specify a resource policy per replica, and not copy the source table policy to all replicas" + }, + "@aws-cdk/aws-ec2:ec2SumTImeoutEnabled": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, initOptions.timeout and resourceSignalTimeout values will be summed together." + }, + "@aws-cdk/aws-appsync:appSyncGraphQLAPIScopeLambdaPermission": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, a Lambda authorizer Permission created when using GraphqlApi will be properly scoped with a SourceArn." + }, + "@aws-cdk/aws-rds:setCorrectValueForDatabaseInstanceReadReplicaInstanceResourceId": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the value of property `instanceResourceId` in construct `DatabaseInstanceReadReplica` will be set to the correct value which is `DbiResourceId` instead of currently `DbInstanceArn`" + }, + "@aws-cdk/core:cfnIncludeRejectComplexResourceUpdateCreatePolicyIntrinsics": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, CFN templates added with `cfn-include` will error if the template contains Resource Update or Create policies with CFN Intrinsics that include non-primitive values." + }, + "@aws-cdk/aws-lambda-nodejs:sdkV3ExcludeSmithyPackages": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, both `@aws-sdk` and `@smithy` packages will be excluded from the Lambda Node.js 18.x runtime to prevent version mismatches in bundled applications." + }, + "@aws-cdk/aws-stepfunctions-tasks:fixRunEcsTaskPolicy": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the resource of IAM Run Ecs policy generated by SFN EcsRunTask will reference the definition, instead of constructing ARN." + }, + "@aws-cdk/aws-ec2:bastionHostUseAmazonLinux2023ByDefault": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the BastionHost construct will use the latest Amazon Linux 2023 AMI, instead of Amazon Linux 2." + }, + "@aws-cdk/core:aspectStabilization": { + "recommendedValue": true, + "explanation": "When enabled, a stabilization loop will be run when invoking Aspects during synthesis.", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/aws-route53-targets:userPoolDomainNameMethodWithoutCustomResource": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, use a new method for DNS Name of user pool domain target without creating a custom resource." + }, + "@aws-cdk/aws-elasticloadbalancingV2:albDualstackWithoutPublicIpv4SecurityGroupRulesDefault": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the default security group ingress rules will allow IPv6 ingress from anywhere" + }, + "@aws-cdk/aws-iam:oidcRejectUnauthorizedConnections": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the default behaviour of OIDC provider will reject unauthorized connections" + }, + "@aws-cdk/core:enableAdditionalMetadataCollection": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, CDK will expand the scope of usage data collected to better inform CDK development and improve communication for security concerns and emerging issues." + }, + "@aws-cdk/aws-lambda:createNewPoliciesWithAddToRolePolicy": { + "userValue": false, + "recommendedValue": false, + "explanation": "[Deprecated] When enabled, Lambda will create new inline policies with AddToRolePolicy instead of adding to the Default Policy Statement" + }, + "@aws-cdk/aws-s3:setUniqueReplicationRoleName": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, CDK will automatically generate a unique role name that is used for s3 object replication." + }, + "@aws-cdk/pipelines:reduceStageRoleTrustScope": { + "recommendedValue": true, + "explanation": "Remove the root account principal from Stage addActions trust policy", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/aws-events:requireEventBusPolicySid": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, grantPutEventsTo() will use resource policies with Statement IDs for service principals." + }, + "@aws-cdk/core:aspectPrioritiesMutating": { + "userValue": true, + "recommendedValue": true, + "explanation": "When set to true, Aspects added by the construct library on your behalf will be given a priority of MUTATING." + }, + "@aws-cdk/aws-dynamodb:retainTableReplica": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, table replica will be default to the removal policy of source table unless specified otherwise." + }, + "@aws-cdk/cognito:logUserPoolClientSecretValue": { + "recommendedValue": false, + "explanation": "When disabled, the value of the user pool client secret will not be logged in the custom resource lambda function logs." + }, + "@aws-cdk/pipelines:reduceCrossAccountActionRoleTrustScope": { + "recommendedValue": true, + "explanation": "When enabled, scopes down the trust policy for the cross-account action role", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/aws-stepfunctions:useDistributedMapResultWriterV2": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the resultWriterV2 property of DistributedMap will be used insted of resultWriter" + }, + "@aws-cdk/s3-notifications:addS3TrustKeyPolicyForSnsSubscriptions": { + "userValue": true, + "recommendedValue": true, + "explanation": "Add an S3 trust policy to a KMS key resource policy for SNS subscriptions." + }, + "@aws-cdk/aws-ec2:requirePrivateSubnetsForEgressOnlyInternetGateway": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, the EgressOnlyGateway resource is only created if private subnets are defined in the dual-stack VPC." + }, + "@aws-cdk/aws-ec2-alpha:useResourceIdForVpcV2Migration": { + "recommendedValue": false, + "explanation": "When enabled, use resource IDs for VPC V2 migration" + }, + "@aws-cdk/aws-s3:publicAccessBlockedByDefault": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, setting any combination of options for BlockPublicAccess will automatically set true for any options not defined." + }, + "@aws-cdk/aws-lambda:useCdkManagedLogGroup": { + "userValue": true, + "recommendedValue": true, + "explanation": "When enabled, CDK creates and manages loggroup for the lambda function" + }, + "@aws-cdk/aws-elasticloadbalancingv2:networkLoadBalancerWithSecurityGroupByDefault": { + "recommendedValue": true, + "explanation": "When enabled, Network Load Balancer will be created with a security group by default." + }, + "@aws-cdk/aws-stepfunctions-tasks:httpInvokeDynamicJsonPathEndpoint": { + "recommendedValue": true, + "explanation": "When enabled, allows using a dynamic apiEndpoint with JSONPath format in HttpInvoke tasks.", + "unconfiguredBehavesLike": { + "v2": true + } + }, + "@aws-cdk/aws-ecs-patterns:uniqueTargetGroupId": { + "recommendedValue": true, + "explanation": "When enabled, ECS patterns will generate unique target group IDs to prevent conflicts during load balancer replacement" + } + } + } } - } + }, + "minimumCliVersion": "2.1031.2" } \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-cloudwatch/test/integ.composite-alarm.js.snapshot/tree.json b/packages/@aws-cdk-testing/framework-integ/test/aws-cloudwatch/test/integ.composite-alarm.js.snapshot/tree.json index da3102f047b38..ece4d8b681bf4 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-cloudwatch/test/integ.composite-alarm.js.snapshot/tree.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-cloudwatch/test/integ.composite-alarm.js.snapshot/tree.json @@ -1,377 +1 @@ -{ - "version": "tree-0.1", - "tree": { - "id": "App", - "path": "", - "children": { - "CompositeAlarmIntegrationTest": { - "id": "CompositeAlarmIntegrationTest", - "path": "CompositeAlarmIntegrationTest", - "children": { - "Alarm1": { - "id": "Alarm1", - "path": "CompositeAlarmIntegrationTest/Alarm1", - "children": { - "Resource": { - "id": "Resource", - "path": "CompositeAlarmIntegrationTest/Alarm1/Resource", - "attributes": { - "aws:cdk:cloudformation:type": "AWS::CloudWatch::Alarm", - "aws:cdk:cloudformation:props": { - "comparisonOperator": "GreaterThanOrEqualToThreshold", - "evaluationPeriods": 3, - "metricName": "Metric", - "namespace": "CDK/Test", - "period": 300, - "statistic": "Average", - "threshold": 100 - } - }, - "constructInfo": { - "fqn": "aws-cdk-lib.aws_cloudwatch.CfnAlarm", - "version": "0.0.0" - } - } - }, - "constructInfo": { - "fqn": "aws-cdk-lib.aws_cloudwatch.Alarm", - "version": "0.0.0" - } - }, - "Alarm2": { - "id": "Alarm2", - "path": "CompositeAlarmIntegrationTest/Alarm2", - "children": { - "Resource": { - "id": "Resource", - "path": "CompositeAlarmIntegrationTest/Alarm2/Resource", - "attributes": { - "aws:cdk:cloudformation:type": "AWS::CloudWatch::Alarm", - "aws:cdk:cloudformation:props": { - "comparisonOperator": "GreaterThanOrEqualToThreshold", - "evaluationPeriods": 3, - "metricName": "Metric", - "namespace": "CDK/Test", - "period": 300, - "statistic": "Average", - "threshold": 1000 - } - }, - "constructInfo": { - "fqn": "aws-cdk-lib.aws_cloudwatch.CfnAlarm", - "version": "0.0.0" - } - } - }, - "constructInfo": { - "fqn": "aws-cdk-lib.aws_cloudwatch.Alarm", - "version": "0.0.0" - } - }, - "Alarm3": { - "id": "Alarm3", - "path": "CompositeAlarmIntegrationTest/Alarm3", - "children": { - "Resource": { - "id": "Resource", - "path": "CompositeAlarmIntegrationTest/Alarm3/Resource", - "attributes": { - "aws:cdk:cloudformation:type": "AWS::CloudWatch::Alarm", - "aws:cdk:cloudformation:props": { - "comparisonOperator": "GreaterThanOrEqualToThreshold", - "evaluationPeriods": 3, - "metricName": "Metric", - "namespace": "CDK/Test", - "period": 300, - "statistic": "Average", - "threshold": 10000 - } - }, - "constructInfo": { - "fqn": "aws-cdk-lib.aws_cloudwatch.CfnAlarm", - "version": "0.0.0" - } - } - }, - "constructInfo": { - "fqn": "aws-cdk-lib.aws_cloudwatch.Alarm", - "version": "0.0.0" - } - }, - "Alarm4": { - "id": "Alarm4", - "path": "CompositeAlarmIntegrationTest/Alarm4", - "children": { - "Resource": { - "id": "Resource", - "path": "CompositeAlarmIntegrationTest/Alarm4/Resource", - "attributes": { - "aws:cdk:cloudformation:type": "AWS::CloudWatch::Alarm", - "aws:cdk:cloudformation:props": { - "comparisonOperator": "GreaterThanOrEqualToThreshold", - "evaluationPeriods": 3, - "metricName": "Metric", - "namespace": "CDK/Test", - "period": 300, - "statistic": "Average", - "threshold": 100000 - } - }, - "constructInfo": { - "fqn": "aws-cdk-lib.aws_cloudwatch.CfnAlarm", - "version": "0.0.0" - } - } - }, - "constructInfo": { - "fqn": "aws-cdk-lib.aws_cloudwatch.Alarm", - "version": "0.0.0" - } - }, - "Alarm5": { - "id": "Alarm5", - "path": "CompositeAlarmIntegrationTest/Alarm5", - "children": { - "Resource": { - "id": "Resource", - "path": "CompositeAlarmIntegrationTest/Alarm5/Resource", - "attributes": { - "aws:cdk:cloudformation:type": "AWS::CloudWatch::Alarm", - "aws:cdk:cloudformation:props": { - "comparisonOperator": "GreaterThanOrEqualToThreshold", - "evaluationPeriods": 3, - "alarmName": "Alarm with space in name", - "metricName": "Metric", - "namespace": "CDK/Test", - "period": 300, - "statistic": "Average", - "threshold": 100000 - } - }, - "constructInfo": { - "fqn": "aws-cdk-lib.aws_cloudwatch.CfnAlarm", - "version": "0.0.0" - } - } - }, - "constructInfo": { - "fqn": "aws-cdk-lib.aws_cloudwatch.Alarm", - "version": "0.0.0" - } - }, - "CompositeAlarm": { - "id": "CompositeAlarm", - "path": "CompositeAlarmIntegrationTest/CompositeAlarm", - "children": { - "Resource": { - "id": "Resource", - "path": "CompositeAlarmIntegrationTest/CompositeAlarm/Resource", - "attributes": { - "aws:cdk:cloudformation:type": "AWS::CloudWatch::CompositeAlarm", - "aws:cdk:cloudformation:props": { - "alarmRule": { - "Fn::Join": [ - "", - [ - "(((ALARM(\"", - { - "Fn::GetAtt": [ - "Alarm1F9009D71", - "Arn" - ] - }, - "\") OR OK(\"", - { - "Fn::GetAtt": [ - "Alarm2A7122E13", - "Arn" - ] - }, - "\") OR ALARM(\"", - { - "Fn::GetAtt": [ - "Alarm32341D8D9", - "Arn" - ] - }, - "\") OR ALARM(\"", - { - "Fn::GetAtt": [ - "Alarm548383B2F", - "Arn" - ] - }, - "\")) AND (NOT (INSUFFICIENT_DATA(\"", - { - "Fn::GetAtt": [ - "Alarm4671832C8", - "Arn" - ] - }, - "\")))) OR FALSE)" - ] - ] - }, - "actionsSuppressor": { - "Fn::GetAtt": [ - "Alarm548383B2F", - "Arn" - ] - }, - "actionsSuppressorExtensionPeriod": 60, - "actionsSuppressorWaitPeriod": 60, - "alarmName": "CompositeAlarmIntegrationTestCompositeAlarm742D2FBA" - } - }, - "constructInfo": { - "fqn": "aws-cdk-lib.aws_cloudwatch.CfnCompositeAlarm", - "version": "0.0.0" - } - } - }, - "constructInfo": { - "fqn": "aws-cdk-lib.aws_cloudwatch.CompositeAlarm", - "version": "0.0.0" - } - }, - "BootstrapVersion": { - "id": "BootstrapVersion", - "path": "CompositeAlarmIntegrationTest/BootstrapVersion", - "constructInfo": { - "fqn": "aws-cdk-lib.CfnParameter", - "version": "0.0.0" - } - }, - "CheckBootstrapVersion": { - "id": "CheckBootstrapVersion", - "path": "CompositeAlarmIntegrationTest/CheckBootstrapVersion", - "constructInfo": { - "fqn": "aws-cdk-lib.CfnRule", - "version": "0.0.0" - } - } - }, - "constructInfo": { - "fqn": "aws-cdk-lib.Stack", - "version": "0.0.0" - } - }, - "CompositeAlarmImportIntegrationTest": { - "id": "CompositeAlarmImportIntegrationTest", - "path": "CompositeAlarmImportIntegrationTest", - "children": { - "alarm": { - "id": "alarm", - "path": "CompositeAlarmImportIntegrationTest/alarm", - "constructInfo": { - "fqn": "aws-cdk-lib.aws_cloudwatch.AlarmBase", - "version": "0.0.0" - } - }, - "AlarmName": { - "id": "AlarmName", - "path": "CompositeAlarmImportIntegrationTest/AlarmName", - "constructInfo": { - "fqn": "aws-cdk-lib.CfnOutput", - "version": "0.0.0" - } - }, - "AlarmArn": { - "id": "AlarmArn", - "path": "CompositeAlarmImportIntegrationTest/AlarmArn", - "constructInfo": { - "fqn": "aws-cdk-lib.CfnOutput", - "version": "0.0.0" - } - }, - "BootstrapVersion": { - "id": "BootstrapVersion", - "path": "CompositeAlarmImportIntegrationTest/BootstrapVersion", - "constructInfo": { - "fqn": "aws-cdk-lib.CfnParameter", - "version": "0.0.0" - } - }, - "CheckBootstrapVersion": { - "id": "CheckBootstrapVersion", - "path": "CompositeAlarmImportIntegrationTest/CheckBootstrapVersion", - "constructInfo": { - "fqn": "aws-cdk-lib.CfnRule", - "version": "0.0.0" - } - } - }, - "constructInfo": { - "fqn": "aws-cdk-lib.Stack", - "version": "0.0.0" - } - }, - "cdk-integ-composite-alarm": { - "id": "cdk-integ-composite-alarm", - "path": "cdk-integ-composite-alarm", - "children": { - "DefaultTest": { - "id": "DefaultTest", - "path": "cdk-integ-composite-alarm/DefaultTest", - "children": { - "Default": { - "id": "Default", - "path": "cdk-integ-composite-alarm/DefaultTest/Default", - "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.270" - } - }, - "DeployAssert": { - "id": "DeployAssert", - "path": "cdk-integ-composite-alarm/DefaultTest/DeployAssert", - "children": { - "BootstrapVersion": { - "id": "BootstrapVersion", - "path": "cdk-integ-composite-alarm/DefaultTest/DeployAssert/BootstrapVersion", - "constructInfo": { - "fqn": "aws-cdk-lib.CfnParameter", - "version": "0.0.0" - } - }, - "CheckBootstrapVersion": { - "id": "CheckBootstrapVersion", - "path": "cdk-integ-composite-alarm/DefaultTest/DeployAssert/CheckBootstrapVersion", - "constructInfo": { - "fqn": "aws-cdk-lib.CfnRule", - "version": "0.0.0" - } - } - }, - "constructInfo": { - "fqn": "aws-cdk-lib.Stack", - "version": "0.0.0" - } - } - }, - "constructInfo": { - "fqn": "@aws-cdk/integ-tests-alpha.IntegTestCase", - "version": "0.0.0" - } - } - }, - "constructInfo": { - "fqn": "@aws-cdk/integ-tests-alpha.IntegTest", - "version": "0.0.0" - } - }, - "Tree": { - "id": "Tree", - "path": "Tree", - "constructInfo": { - "fqn": "constructs.Construct", - "version": "10.1.270" - } - } - }, - "constructInfo": { - "fqn": "aws-cdk-lib.App", - "version": "0.0.0" - } - } -} \ No newline at end of file +{"version":"tree-0.1","tree":{"id":"App","path":"","constructInfo":{"fqn":"aws-cdk-lib.App","version":"0.0.0"},"children":{"CompositeAlarmIntegrationTest":{"id":"CompositeAlarmIntegrationTest","path":"CompositeAlarmIntegrationTest","constructInfo":{"fqn":"aws-cdk-lib.Stack","version":"0.0.0"},"children":{"Alarm1":{"id":"Alarm1","path":"CompositeAlarmIntegrationTest/Alarm1","constructInfo":{"fqn":"aws-cdk-lib.aws_cloudwatch.Alarm","version":"0.0.0","metadata":[{"metric":{"warnings":"*","warningsV2":"*"},"threshold":"*","evaluationPeriods":"*"}]},"children":{"Resource":{"id":"Resource","path":"CompositeAlarmIntegrationTest/Alarm1/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_cloudwatch.CfnAlarm","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::CloudWatch::Alarm","aws:cdk:cloudformation:props":{"comparisonOperator":"GreaterThanOrEqualToThreshold","evaluationPeriods":3,"metricName":"Metric","namespace":"CDK/Test","period":300,"statistic":"Average","threshold":100}}}}},"Alarm2":{"id":"Alarm2","path":"CompositeAlarmIntegrationTest/Alarm2","constructInfo":{"fqn":"aws-cdk-lib.aws_cloudwatch.Alarm","version":"0.0.0","metadata":[{"metric":{"warnings":"*","warningsV2":"*"},"threshold":"*","evaluationPeriods":"*"}]},"children":{"Resource":{"id":"Resource","path":"CompositeAlarmIntegrationTest/Alarm2/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_cloudwatch.CfnAlarm","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::CloudWatch::Alarm","aws:cdk:cloudformation:props":{"comparisonOperator":"GreaterThanOrEqualToThreshold","evaluationPeriods":3,"metricName":"Metric","namespace":"CDK/Test","period":300,"statistic":"Average","threshold":1000}}}}},"Alarm3":{"id":"Alarm3","path":"CompositeAlarmIntegrationTest/Alarm3","constructInfo":{"fqn":"aws-cdk-lib.aws_cloudwatch.Alarm","version":"0.0.0","metadata":[{"metric":{"warnings":"*","warningsV2":"*"},"threshold":"*","evaluationPeriods":"*"}]},"children":{"Resource":{"id":"Resource","path":"CompositeAlarmIntegrationTest/Alarm3/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_cloudwatch.CfnAlarm","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::CloudWatch::Alarm","aws:cdk:cloudformation:props":{"comparisonOperator":"GreaterThanOrEqualToThreshold","evaluationPeriods":3,"metricName":"Metric","namespace":"CDK/Test","period":300,"statistic":"Average","threshold":10000}}}}},"Alarm4":{"id":"Alarm4","path":"CompositeAlarmIntegrationTest/Alarm4","constructInfo":{"fqn":"aws-cdk-lib.aws_cloudwatch.Alarm","version":"0.0.0","metadata":[{"metric":{"warnings":"*","warningsV2":"*"},"threshold":"*","evaluationPeriods":"*"}]},"children":{"Resource":{"id":"Resource","path":"CompositeAlarmIntegrationTest/Alarm4/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_cloudwatch.CfnAlarm","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::CloudWatch::Alarm","aws:cdk:cloudformation:props":{"comparisonOperator":"GreaterThanOrEqualToThreshold","evaluationPeriods":3,"metricName":"Metric","namespace":"CDK/Test","period":300,"statistic":"Average","threshold":100000}}}}},"Alarm5":{"id":"Alarm5","path":"CompositeAlarmIntegrationTest/Alarm5","constructInfo":{"fqn":"aws-cdk-lib.aws_cloudwatch.Alarm","version":"0.0.0","metadata":[{"alarmName":"*","metric":{"warnings":"*","warningsV2":"*"},"threshold":"*","evaluationPeriods":"*"}]},"children":{"Resource":{"id":"Resource","path":"CompositeAlarmIntegrationTest/Alarm5/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_cloudwatch.CfnAlarm","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::CloudWatch::Alarm","aws:cdk:cloudformation:props":{"alarmName":"Alarm with space in name","comparisonOperator":"GreaterThanOrEqualToThreshold","evaluationPeriods":3,"metricName":"Metric","namespace":"CDK/Test","period":300,"statistic":"Average","threshold":100000}}}}},"CompositeAlarm":{"id":"CompositeAlarm","path":"CompositeAlarmIntegrationTest/CompositeAlarm","constructInfo":{"fqn":"aws-cdk-lib.aws_cloudwatch.CompositeAlarm","version":"0.0.0","metadata":[{"alarmRule":"*","actionsSuppressor":"*"}]},"children":{"Resource":{"id":"Resource","path":"CompositeAlarmIntegrationTest/CompositeAlarm/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_cloudwatch.CfnCompositeAlarm","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::CloudWatch::CompositeAlarm","aws:cdk:cloudformation:props":{"actionsSuppressor":{"Fn::GetAtt":["Alarm548383B2F","Arn"]},"actionsSuppressorExtensionPeriod":60,"actionsSuppressorWaitPeriod":60,"alarmName":"CompositeAlarmIntegrationTestCompositeAlarm742D2FBA","alarmRule":{"Fn::Join":["",["(((ALARM(\"",{"Fn::GetAtt":["Alarm1F9009D71","Arn"]},"\") OR OK(\"",{"Fn::GetAtt":["Alarm2A7122E13","Arn"]},"\") OR ALARM(\"",{"Fn::GetAtt":["Alarm32341D8D9","Arn"]},"\") OR ALARM(\"",{"Fn::GetAtt":["Alarm548383B2F","Arn"]},"\")) AND (NOT (INSUFFICIENT_DATA(\"",{"Fn::GetAtt":["Alarm4671832C8","Arn"]},"\"))) AND AT_LEAST(2, ALARM , (",{"Fn::GetAtt":["Alarm1F9009D71","Arn"]},", ",{"Fn::GetAtt":["Alarm2A7122E13","Arn"]},", ",{"Fn::GetAtt":["Alarm32341D8D9","Arn"]},")) AND AT_LEAST(60%, NOT OK , (",{"Fn::GetAtt":["Alarm1F9009D71","Arn"]},", ",{"Fn::GetAtt":["Alarm2A7122E13","Arn"]},", ",{"Fn::GetAtt":["Alarm32341D8D9","Arn"]},"))) OR FALSE)"]]}}}}}},"BootstrapVersion":{"id":"BootstrapVersion","path":"CompositeAlarmIntegrationTest/BootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnParameter","version":"0.0.0"}},"CheckBootstrapVersion":{"id":"CheckBootstrapVersion","path":"CompositeAlarmIntegrationTest/CheckBootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnRule","version":"0.0.0"}}}},"CompositeAlarmImportIntegrationTest":{"id":"CompositeAlarmImportIntegrationTest","path":"CompositeAlarmImportIntegrationTest","constructInfo":{"fqn":"aws-cdk-lib.Stack","version":"0.0.0"},"children":{"alarm":{"id":"alarm","path":"CompositeAlarmImportIntegrationTest/alarm","constructInfo":{"fqn":"aws-cdk-lib.aws_cloudwatch.AlarmBase","version":"0.0.0","metadata":[]}},"AlarmName":{"id":"AlarmName","path":"CompositeAlarmImportIntegrationTest/AlarmName","constructInfo":{"fqn":"aws-cdk-lib.CfnOutput","version":"0.0.0"}},"AlarmArn":{"id":"AlarmArn","path":"CompositeAlarmImportIntegrationTest/AlarmArn","constructInfo":{"fqn":"aws-cdk-lib.CfnOutput","version":"0.0.0"}},"BootstrapVersion":{"id":"BootstrapVersion","path":"CompositeAlarmImportIntegrationTest/BootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnParameter","version":"0.0.0"}},"CheckBootstrapVersion":{"id":"CheckBootstrapVersion","path":"CompositeAlarmImportIntegrationTest/CheckBootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnRule","version":"0.0.0"}}}},"cdk-integ-composite-alarm":{"id":"cdk-integ-composite-alarm","path":"cdk-integ-composite-alarm","constructInfo":{"fqn":"@aws-cdk/integ-tests-alpha.IntegTest","version":"0.0.0"},"children":{"DefaultTest":{"id":"DefaultTest","path":"cdk-integ-composite-alarm/DefaultTest","constructInfo":{"fqn":"@aws-cdk/integ-tests-alpha.IntegTestCase","version":"0.0.0"},"children":{"Default":{"id":"Default","path":"cdk-integ-composite-alarm/DefaultTest/Default","constructInfo":{"fqn":"constructs.Construct","version":"10.4.2"}},"DeployAssert":{"id":"DeployAssert","path":"cdk-integ-composite-alarm/DefaultTest/DeployAssert","constructInfo":{"fqn":"aws-cdk-lib.Stack","version":"0.0.0"},"children":{"BootstrapVersion":{"id":"BootstrapVersion","path":"cdk-integ-composite-alarm/DefaultTest/DeployAssert/BootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnParameter","version":"0.0.0"}},"CheckBootstrapVersion":{"id":"CheckBootstrapVersion","path":"cdk-integ-composite-alarm/DefaultTest/DeployAssert/CheckBootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnRule","version":"0.0.0"}}}}}}}},"Tree":{"id":"Tree","path":"Tree","constructInfo":{"fqn":"constructs.Construct","version":"10.4.2"}}}}} \ No newline at end of file diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-cloudwatch/test/integ.composite-alarm.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-cloudwatch/test/integ.composite-alarm.ts index 934f83ef0af89..b6c30d6f97170 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-cloudwatch/test/integ.composite-alarm.ts +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-cloudwatch/test/integ.composite-alarm.ts @@ -51,6 +51,14 @@ class CompositeAlarmIntegrationTest extends Stack { alarm5, ), AlarmRule.not(AlarmRule.fromAlarm(alarm4, AlarmState.INSUFFICIENT_DATA)), + AlarmRule.hasAtLeastAlarm({ + operands: [alarm1, alarm2, alarm3], + count: 2, + }), + AlarmRule.hasAtLeastNotOk({ + operands: [alarm1, alarm2, alarm3], + percentage: 60, + }), ), AlarmRule.fromBoolean(false), ); diff --git a/packages/aws-cdk-lib/aws-cloudwatch/README.md b/packages/aws-cdk-lib/aws-cloudwatch/README.md index 5902054ccd3bc..84108eea63aa9 100644 --- a/packages/aws-cdk-lib/aws-cloudwatch/README.md +++ b/packages/aws-cdk-lib/aws-cloudwatch/README.md @@ -399,6 +399,10 @@ const alarmRule = cloudwatch.AlarmRule.anyOf( alarm1, cloudwatch.AlarmRule.fromAlarm(alarm2, cloudwatch.AlarmState.OK), alarm3, + cloudwatch.AlarmRule.hasAtLeastAlarm({ + operands: [alarm1, alarm2, alarm3], + count: 1, + }), ), cloudwatch.AlarmRule.not(cloudwatch.AlarmRule.fromAlarm(alarm4, cloudwatch.AlarmState.INSUFFICIENT_DATA)), ), diff --git a/packages/aws-cdk-lib/aws-cloudwatch/lib/alarm-rule.ts b/packages/aws-cdk-lib/aws-cloudwatch/lib/alarm-rule.ts index 277e90db5ce21..abab8802ab639 100644 --- a/packages/aws-cdk-lib/aws-cloudwatch/lib/alarm-rule.ts +++ b/packages/aws-cdk-lib/aws-cloudwatch/lib/alarm-rule.ts @@ -34,6 +34,34 @@ enum Operator { } +/** + * Options to AT_LEAST AlarmRule wrapper function + */ +export interface HasAtLeastOptions { + + /** + * function to wrap provided AlarmRule in AT_LEAST expression. + */ + readonly operands: IAlarm[]; + + /** + * minimum number of specified alarms + * + * Units: Count + * + * @default - Exactly one of `count`, `percentage` is required. + */ + readonly count?: number; + /** + * minimum percentage of specified alarms + * + * Units: Percentage + * + * @default - Exactly one of `count`, `percentage` is required. + */ + readonly percentage?: number; +} + /** * Class with static functions to build AlarmRule for Composite Alarms. */ @@ -69,6 +97,66 @@ export class AlarmRule { }; } + /** + * function to wrap provided AlarmRule in AT_LEAST expression for ALARM state. + * + * @param options options for creating a new AlarmRule. + */ + public static hasAtLeastAlarm(options: HasAtLeastOptions): IAlarmRule { + const alarmState = `${AlarmState.ALARM}`; + return this.hasAtLeast(alarmState, options); + } + + /** + * function to wrap provided AlarmRule in AT_LEAST expression for OK state. + * + * @param options options for creating a new AlarmRule. + */ + public static hasAtLeastOk(options: HasAtLeastOptions): IAlarmRule { + const alarmState = `${AlarmState.OK}`; + return this.hasAtLeast(alarmState, options); + } + + /** + * function to wrap provided AlarmRule in AT_LEAST expression for INSUFFICIENT_DATA state. + * + * @param options options for creating a new AlarmRule. + */ + public static hasAtLeastInsufficient(options: HasAtLeastOptions): IAlarmRule { + const alarmState = `${AlarmState.INSUFFICIENT_DATA}`; + return this.hasAtLeast(alarmState, options); + } + + /** + * function to wrap provided AlarmRule in AT_LEAST expression for NOT ALARM state. + * + * @param options options for creating a new AlarmRule. + */ + public static hasAtLeastNotAlarm(options: HasAtLeastOptions): IAlarmRule { + const alarmState = `${Operator.NOT} ${AlarmState.ALARM}`; + return this.hasAtLeast(alarmState, options); + } + + /** + * function to wrap provided AlarmRule in AT_LEAST expression for NOT OK state. + * + * @param options options for creating a new AlarmRule. + */ + public static hasAtLeastNotOk(options: HasAtLeastOptions): IAlarmRule { + const alarmState = `${Operator.NOT} ${AlarmState.OK}`; + return this.hasAtLeast(alarmState, options); + } + + /** + * function to wrap provided AlarmRule in AT_LEAST expression for NOT INSUFFICIENT_DATA state. + * + * @param options options for creating a new AlarmRule. + */ + public static hasAtLeastNotInsufficient(options: HasAtLeastOptions): IAlarmRule { + const alarmState = `${Operator.NOT} ${AlarmState.INSUFFICIENT_DATA}`; + return this.hasAtLeast(alarmState, options); + } + /** * function to build TRUE/FALSE intent for Rule Expression. * @@ -123,4 +211,34 @@ export class AlarmRule { } }; } + + private static hasAtLeast(alarmState: string, options: HasAtLeastOptions): IAlarmRule { + return new class implements IAlarmRule { + public renderAlarmRule(): string { + if (options.operands.length === 0) { + throw new UnscopedValidationError(`Did not detect any operands for AT_LEAST ${alarmState}`); + } + + if ((options.count !== undefined) === (options.percentage !== undefined)) { + throw new UnscopedValidationError('Specify exactly one of \'count\' and \'percentage\''); + } + + if (options.count !== undefined && (options.count < 1 || options.operands.length < options.count + || !Number.isInteger(options.count))) { + throw new UnscopedValidationError(`count must be between 1 and alarm length(${options.operands.length}) integer, got ${options.count}`); + } + + if (options.percentage !== undefined && (options.percentage < 1 + || 100 < options.percentage || !Number.isInteger(options.percentage))) { + throw new UnscopedValidationError(`percentage must be between 1 and 100, got ${options.percentage}`); + } + + const threshold = options.count || `${options.percentage}%`; + const concatAlarms = options.operands + .map(operand => `${operand.alarmArn}`) + .join(', '); + return `AT_LEAST(${threshold}, ${alarmState} , (${concatAlarms}))`; + } + }; + } } diff --git a/packages/aws-cdk-lib/aws-cloudwatch/test/composite-alarm.test.ts b/packages/aws-cdk-lib/aws-cloudwatch/test/composite-alarm.test.ts index 8b4f1058fce7a..a9425e7b5da6a 100644 --- a/packages/aws-cdk-lib/aws-cloudwatch/test/composite-alarm.test.ts +++ b/packages/aws-cdk-lib/aws-cloudwatch/test/composite-alarm.test.ts @@ -51,6 +51,14 @@ describe('CompositeAlarm', () => { alarm5, ), AlarmRule.not(AlarmRule.fromAlarm(alarm4, AlarmState.INSUFFICIENT_DATA)), + AlarmRule.hasAtLeastAlarm({ + operands: [alarm1, alarm2, alarm3], + count: 2, + }), + AlarmRule.hasAtLeastNotOk({ + operands: [alarm1, alarm2, alarm3], + percentage: 60, + }), ), AlarmRule.fromBoolean(false), ); @@ -100,7 +108,19 @@ describe('CompositeAlarm', () => { 'Arn', ], }, - '")))) OR FALSE)', + '"))) AND AT_LEAST(2, ALARM , (', + { 'Fn::GetAtt': ['Alarm1F9009D71', 'Arn'] }, + ', ', + { 'Fn::GetAtt': ['Alarm2A7122E13', 'Arn'] }, + ', ', + { 'Fn::GetAtt': ['Alarm32341D8D9', 'Arn'] }, + ')) AND AT_LEAST(60%, NOT OK , (', + { 'Fn::GetAtt': ['Alarm1F9009D71', 'Arn'] }, + ', ', + { 'Fn::GetAtt': ['Alarm2A7122E13', 'Arn'] }, + ', ', + { 'Fn::GetAtt': ['Alarm32341D8D9', 'Arn'] }, + '))) OR FALSE)', ], ], }, @@ -216,4 +236,67 @@ describe('CompositeAlarm', () => { alarmRule: AlarmRule.allOf(), })).toThrow('Did not detect any operands for AlarmRule.allOf'); }); + + test('empty hasAtLeast', () => { + expect(() => new CompositeAlarm(new Stack(), 'alarm', { + alarmRule: AlarmRule.hasAtLeastAlarm({ operands: [], percentage: 1 }), + })).toThrow('Did not detect any operands for AT_LEAST ALARM'); + }); + + test.each([{ count: 1, percentage: 1 }, {}])('specify exactly property for hasAtLeast: %s', (threshold) => { + const stack = new Stack(); + + const testMetric = new Metric({ + namespace: 'CDK/Test', + metricName: 'Metric', + }); + + const alarm = new Alarm(stack, 'Alarm1', { + metric: testMetric, + threshold: 100, + evaluationPeriods: 3, + }); + + expect(() => new CompositeAlarm(new Stack(), 'alarm', { + alarmRule: AlarmRule.hasAtLeastInsufficient({ operands: [alarm], ...threshold }), + })).toThrow('Specify exactly one of \'count\' and \'percentage\''); + }); + + test.each([0, 3, 1.5])('count of hasAtLeast: %s', (count: number) => { + const stack = new Stack(); + + const testMetric = new Metric({ + namespace: 'CDK/Test', + metricName: 'Metric', + }); + + const alarm = new Alarm(stack, 'Alarm1', { + metric: testMetric, + threshold: 100, + evaluationPeriods: 3, + }); + + expect(() => new CompositeAlarm(new Stack(), 'alarm', { + alarmRule: AlarmRule.hasAtLeastOk({ operands: [alarm], count }), + })).toThrow(`count must be between 1 and alarm length(1) integer, got ${count}`); + }); + + test.each([0, 101, 1.5])('percentage of hasAtLeast: %s%', (percentage: number) => { + const stack = new Stack(); + + const testMetric = new Metric({ + namespace: 'CDK/Test', + metricName: 'Metric', + }); + + const alarm = new Alarm(stack, 'Alarm1', { + metric: testMetric, + threshold: 100, + evaluationPeriods: 3, + }); + + expect(() => new CompositeAlarm(new Stack(), 'alarm', { + alarmRule: AlarmRule.hasAtLeastOk({ operands: [alarm], percentage }), + })).toThrow(`percentage must be between 1 and 100, got ${percentage}`); + }); }); From 311ab3f6fc6d6a8e8d8dba766f55e404a8a7f894 Mon Sep 17 00:00:00 2001 From: aki-kii <65025532+aki-kii@users.noreply.github.com> Date: Wed, 19 Nov 2025 23:20:31 +0900 Subject: [PATCH 2/9] Update packages/aws-cdk-lib/aws-cloudwatch/lib/alarm-rule.ts Co-authored-by: Kenta Goto <24818752+go-to-k@users.noreply.github.com> --- packages/aws-cdk-lib/aws-cloudwatch/lib/alarm-rule.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/aws-cdk-lib/aws-cloudwatch/lib/alarm-rule.ts b/packages/aws-cdk-lib/aws-cloudwatch/lib/alarm-rule.ts index abab8802ab639..e9528fe404ddb 100644 --- a/packages/aws-cdk-lib/aws-cloudwatch/lib/alarm-rule.ts +++ b/packages/aws-cdk-lib/aws-cloudwatch/lib/alarm-rule.ts @@ -237,7 +237,7 @@ export class AlarmRule { const concatAlarms = options.operands .map(operand => `${operand.alarmArn}`) .join(', '); - return `AT_LEAST(${threshold}, ${alarmState} , (${concatAlarms}))`; + return `AT_LEAST(${threshold}, ${alarmState}, (${concatAlarms}))`; } }; } From 53fbbd56d89f0df6772e5d1e91251c1f81d4d930 Mon Sep 17 00:00:00 2001 From: aki-kii <65025532+aki-kii@users.noreply.github.com> Date: Wed, 19 Nov 2025 23:20:40 +0900 Subject: [PATCH 3/9] Update packages/aws-cdk-lib/aws-cloudwatch/test/composite-alarm.test.ts Co-authored-by: Kenta Goto <24818752+go-to-k@users.noreply.github.com> --- .../aws-cdk-lib/aws-cloudwatch/test/composite-alarm.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/aws-cdk-lib/aws-cloudwatch/test/composite-alarm.test.ts b/packages/aws-cdk-lib/aws-cloudwatch/test/composite-alarm.test.ts index a9425e7b5da6a..6f1c5cd960a10 100644 --- a/packages/aws-cdk-lib/aws-cloudwatch/test/composite-alarm.test.ts +++ b/packages/aws-cdk-lib/aws-cloudwatch/test/composite-alarm.test.ts @@ -237,7 +237,7 @@ describe('CompositeAlarm', () => { })).toThrow('Did not detect any operands for AlarmRule.allOf'); }); - test('empty hasAtLeast', () => { + test('empty operands for hasAtLeast', () => { expect(() => new CompositeAlarm(new Stack(), 'alarm', { alarmRule: AlarmRule.hasAtLeastAlarm({ operands: [], percentage: 1 }), })).toThrow('Did not detect any operands for AT_LEAST ALARM'); From b862d6a36d67e43cfec96d24a12074aa2c5fc8ae Mon Sep 17 00:00:00 2001 From: aki-kii Date: Wed, 19 Nov 2025 23:36:55 +0900 Subject: [PATCH 4/9] fix: integ and unit tests --- .../CompositeAlarmIntegrationTest.assets.json | 6 +++--- .../CompositeAlarmIntegrationTest.template.json | 4 ++-- .../test/integ.composite-alarm.js.snapshot/manifest.json | 2 +- .../test/integ.composite-alarm.js.snapshot/tree.json | 2 +- .../aws-cdk-lib/aws-cloudwatch/test/composite-alarm.test.ts | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-cloudwatch/test/integ.composite-alarm.js.snapshot/CompositeAlarmIntegrationTest.assets.json b/packages/@aws-cdk-testing/framework-integ/test/aws-cloudwatch/test/integ.composite-alarm.js.snapshot/CompositeAlarmIntegrationTest.assets.json index 234931b692c6b..dcd6635c80a96 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-cloudwatch/test/integ.composite-alarm.js.snapshot/CompositeAlarmIntegrationTest.assets.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-cloudwatch/test/integ.composite-alarm.js.snapshot/CompositeAlarmIntegrationTest.assets.json @@ -1,16 +1,16 @@ { "version": "48.0.0", "files": { - "3943b179d9074a1006b8024b5a71901e57466caf9c2a157b6a20fd75f1faa1fd": { + "8284aa5320b6b544ab48fdc0420699ed873087ee3c1007224ffefea31e22caf2": { "displayName": "CompositeAlarmIntegrationTest Template", "source": { "path": "CompositeAlarmIntegrationTest.template.json", "packaging": "file" }, "destinations": { - "current_account-current_region-77901b19": { + "current_account-current_region-de3be2dc": { "bucketName": "cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}", - "objectKey": "3943b179d9074a1006b8024b5a71901e57466caf9c2a157b6a20fd75f1faa1fd.json", + "objectKey": "8284aa5320b6b544ab48fdc0420699ed873087ee3c1007224ffefea31e22caf2.json", "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-file-publishing-role-${AWS::AccountId}-${AWS::Region}" } } diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-cloudwatch/test/integ.composite-alarm.js.snapshot/CompositeAlarmIntegrationTest.template.json b/packages/@aws-cdk-testing/framework-integ/test/aws-cloudwatch/test/integ.composite-alarm.js.snapshot/CompositeAlarmIntegrationTest.template.json index 044aafebe9f44..5dd128f4b10c6 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-cloudwatch/test/integ.composite-alarm.js.snapshot/CompositeAlarmIntegrationTest.template.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-cloudwatch/test/integ.composite-alarm.js.snapshot/CompositeAlarmIntegrationTest.template.json @@ -112,7 +112,7 @@ "Arn" ] }, - "\"))) AND AT_LEAST(2, ALARM , (", + "\"))) AND AT_LEAST(2, ALARM, (", { "Fn::GetAtt": [ "Alarm1F9009D71", @@ -133,7 +133,7 @@ "Arn" ] }, - ")) AND AT_LEAST(60%, NOT OK , (", + ")) AND AT_LEAST(60%, NOT OK, (", { "Fn::GetAtt": [ "Alarm1F9009D71", diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-cloudwatch/test/integ.composite-alarm.js.snapshot/manifest.json b/packages/@aws-cdk-testing/framework-integ/test/aws-cloudwatch/test/integ.composite-alarm.js.snapshot/manifest.json index ce7fc2ef3a343..863d6040cd7fa 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-cloudwatch/test/integ.composite-alarm.js.snapshot/manifest.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-cloudwatch/test/integ.composite-alarm.js.snapshot/manifest.json @@ -18,7 +18,7 @@ "validateOnSynth": false, "assumeRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-deploy-role-${AWS::AccountId}-${AWS::Region}", "cloudFormationExecutionRoleArn": "arn:${AWS::Partition}:iam::${AWS::AccountId}:role/cdk-hnb659fds-cfn-exec-role-${AWS::AccountId}-${AWS::Region}", - "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/3943b179d9074a1006b8024b5a71901e57466caf9c2a157b6a20fd75f1faa1fd.json", + "stackTemplateAssetObjectUrl": "s3://cdk-hnb659fds-assets-${AWS::AccountId}-${AWS::Region}/8284aa5320b6b544ab48fdc0420699ed873087ee3c1007224ffefea31e22caf2.json", "requiresBootstrapStackVersion": 6, "bootstrapStackVersionSsmParameter": "/cdk-bootstrap/hnb659fds/version", "additionalDependencies": [ diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-cloudwatch/test/integ.composite-alarm.js.snapshot/tree.json b/packages/@aws-cdk-testing/framework-integ/test/aws-cloudwatch/test/integ.composite-alarm.js.snapshot/tree.json index ece4d8b681bf4..96dd6d551f307 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-cloudwatch/test/integ.composite-alarm.js.snapshot/tree.json +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-cloudwatch/test/integ.composite-alarm.js.snapshot/tree.json @@ -1 +1 @@ -{"version":"tree-0.1","tree":{"id":"App","path":"","constructInfo":{"fqn":"aws-cdk-lib.App","version":"0.0.0"},"children":{"CompositeAlarmIntegrationTest":{"id":"CompositeAlarmIntegrationTest","path":"CompositeAlarmIntegrationTest","constructInfo":{"fqn":"aws-cdk-lib.Stack","version":"0.0.0"},"children":{"Alarm1":{"id":"Alarm1","path":"CompositeAlarmIntegrationTest/Alarm1","constructInfo":{"fqn":"aws-cdk-lib.aws_cloudwatch.Alarm","version":"0.0.0","metadata":[{"metric":{"warnings":"*","warningsV2":"*"},"threshold":"*","evaluationPeriods":"*"}]},"children":{"Resource":{"id":"Resource","path":"CompositeAlarmIntegrationTest/Alarm1/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_cloudwatch.CfnAlarm","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::CloudWatch::Alarm","aws:cdk:cloudformation:props":{"comparisonOperator":"GreaterThanOrEqualToThreshold","evaluationPeriods":3,"metricName":"Metric","namespace":"CDK/Test","period":300,"statistic":"Average","threshold":100}}}}},"Alarm2":{"id":"Alarm2","path":"CompositeAlarmIntegrationTest/Alarm2","constructInfo":{"fqn":"aws-cdk-lib.aws_cloudwatch.Alarm","version":"0.0.0","metadata":[{"metric":{"warnings":"*","warningsV2":"*"},"threshold":"*","evaluationPeriods":"*"}]},"children":{"Resource":{"id":"Resource","path":"CompositeAlarmIntegrationTest/Alarm2/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_cloudwatch.CfnAlarm","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::CloudWatch::Alarm","aws:cdk:cloudformation:props":{"comparisonOperator":"GreaterThanOrEqualToThreshold","evaluationPeriods":3,"metricName":"Metric","namespace":"CDK/Test","period":300,"statistic":"Average","threshold":1000}}}}},"Alarm3":{"id":"Alarm3","path":"CompositeAlarmIntegrationTest/Alarm3","constructInfo":{"fqn":"aws-cdk-lib.aws_cloudwatch.Alarm","version":"0.0.0","metadata":[{"metric":{"warnings":"*","warningsV2":"*"},"threshold":"*","evaluationPeriods":"*"}]},"children":{"Resource":{"id":"Resource","path":"CompositeAlarmIntegrationTest/Alarm3/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_cloudwatch.CfnAlarm","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::CloudWatch::Alarm","aws:cdk:cloudformation:props":{"comparisonOperator":"GreaterThanOrEqualToThreshold","evaluationPeriods":3,"metricName":"Metric","namespace":"CDK/Test","period":300,"statistic":"Average","threshold":10000}}}}},"Alarm4":{"id":"Alarm4","path":"CompositeAlarmIntegrationTest/Alarm4","constructInfo":{"fqn":"aws-cdk-lib.aws_cloudwatch.Alarm","version":"0.0.0","metadata":[{"metric":{"warnings":"*","warningsV2":"*"},"threshold":"*","evaluationPeriods":"*"}]},"children":{"Resource":{"id":"Resource","path":"CompositeAlarmIntegrationTest/Alarm4/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_cloudwatch.CfnAlarm","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::CloudWatch::Alarm","aws:cdk:cloudformation:props":{"comparisonOperator":"GreaterThanOrEqualToThreshold","evaluationPeriods":3,"metricName":"Metric","namespace":"CDK/Test","period":300,"statistic":"Average","threshold":100000}}}}},"Alarm5":{"id":"Alarm5","path":"CompositeAlarmIntegrationTest/Alarm5","constructInfo":{"fqn":"aws-cdk-lib.aws_cloudwatch.Alarm","version":"0.0.0","metadata":[{"alarmName":"*","metric":{"warnings":"*","warningsV2":"*"},"threshold":"*","evaluationPeriods":"*"}]},"children":{"Resource":{"id":"Resource","path":"CompositeAlarmIntegrationTest/Alarm5/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_cloudwatch.CfnAlarm","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::CloudWatch::Alarm","aws:cdk:cloudformation:props":{"alarmName":"Alarm with space in name","comparisonOperator":"GreaterThanOrEqualToThreshold","evaluationPeriods":3,"metricName":"Metric","namespace":"CDK/Test","period":300,"statistic":"Average","threshold":100000}}}}},"CompositeAlarm":{"id":"CompositeAlarm","path":"CompositeAlarmIntegrationTest/CompositeAlarm","constructInfo":{"fqn":"aws-cdk-lib.aws_cloudwatch.CompositeAlarm","version":"0.0.0","metadata":[{"alarmRule":"*","actionsSuppressor":"*"}]},"children":{"Resource":{"id":"Resource","path":"CompositeAlarmIntegrationTest/CompositeAlarm/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_cloudwatch.CfnCompositeAlarm","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::CloudWatch::CompositeAlarm","aws:cdk:cloudformation:props":{"actionsSuppressor":{"Fn::GetAtt":["Alarm548383B2F","Arn"]},"actionsSuppressorExtensionPeriod":60,"actionsSuppressorWaitPeriod":60,"alarmName":"CompositeAlarmIntegrationTestCompositeAlarm742D2FBA","alarmRule":{"Fn::Join":["",["(((ALARM(\"",{"Fn::GetAtt":["Alarm1F9009D71","Arn"]},"\") OR OK(\"",{"Fn::GetAtt":["Alarm2A7122E13","Arn"]},"\") OR ALARM(\"",{"Fn::GetAtt":["Alarm32341D8D9","Arn"]},"\") OR ALARM(\"",{"Fn::GetAtt":["Alarm548383B2F","Arn"]},"\")) AND (NOT (INSUFFICIENT_DATA(\"",{"Fn::GetAtt":["Alarm4671832C8","Arn"]},"\"))) AND AT_LEAST(2, ALARM , (",{"Fn::GetAtt":["Alarm1F9009D71","Arn"]},", ",{"Fn::GetAtt":["Alarm2A7122E13","Arn"]},", ",{"Fn::GetAtt":["Alarm32341D8D9","Arn"]},")) AND AT_LEAST(60%, NOT OK , (",{"Fn::GetAtt":["Alarm1F9009D71","Arn"]},", ",{"Fn::GetAtt":["Alarm2A7122E13","Arn"]},", ",{"Fn::GetAtt":["Alarm32341D8D9","Arn"]},"))) OR FALSE)"]]}}}}}},"BootstrapVersion":{"id":"BootstrapVersion","path":"CompositeAlarmIntegrationTest/BootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnParameter","version":"0.0.0"}},"CheckBootstrapVersion":{"id":"CheckBootstrapVersion","path":"CompositeAlarmIntegrationTest/CheckBootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnRule","version":"0.0.0"}}}},"CompositeAlarmImportIntegrationTest":{"id":"CompositeAlarmImportIntegrationTest","path":"CompositeAlarmImportIntegrationTest","constructInfo":{"fqn":"aws-cdk-lib.Stack","version":"0.0.0"},"children":{"alarm":{"id":"alarm","path":"CompositeAlarmImportIntegrationTest/alarm","constructInfo":{"fqn":"aws-cdk-lib.aws_cloudwatch.AlarmBase","version":"0.0.0","metadata":[]}},"AlarmName":{"id":"AlarmName","path":"CompositeAlarmImportIntegrationTest/AlarmName","constructInfo":{"fqn":"aws-cdk-lib.CfnOutput","version":"0.0.0"}},"AlarmArn":{"id":"AlarmArn","path":"CompositeAlarmImportIntegrationTest/AlarmArn","constructInfo":{"fqn":"aws-cdk-lib.CfnOutput","version":"0.0.0"}},"BootstrapVersion":{"id":"BootstrapVersion","path":"CompositeAlarmImportIntegrationTest/BootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnParameter","version":"0.0.0"}},"CheckBootstrapVersion":{"id":"CheckBootstrapVersion","path":"CompositeAlarmImportIntegrationTest/CheckBootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnRule","version":"0.0.0"}}}},"cdk-integ-composite-alarm":{"id":"cdk-integ-composite-alarm","path":"cdk-integ-composite-alarm","constructInfo":{"fqn":"@aws-cdk/integ-tests-alpha.IntegTest","version":"0.0.0"},"children":{"DefaultTest":{"id":"DefaultTest","path":"cdk-integ-composite-alarm/DefaultTest","constructInfo":{"fqn":"@aws-cdk/integ-tests-alpha.IntegTestCase","version":"0.0.0"},"children":{"Default":{"id":"Default","path":"cdk-integ-composite-alarm/DefaultTest/Default","constructInfo":{"fqn":"constructs.Construct","version":"10.4.2"}},"DeployAssert":{"id":"DeployAssert","path":"cdk-integ-composite-alarm/DefaultTest/DeployAssert","constructInfo":{"fqn":"aws-cdk-lib.Stack","version":"0.0.0"},"children":{"BootstrapVersion":{"id":"BootstrapVersion","path":"cdk-integ-composite-alarm/DefaultTest/DeployAssert/BootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnParameter","version":"0.0.0"}},"CheckBootstrapVersion":{"id":"CheckBootstrapVersion","path":"cdk-integ-composite-alarm/DefaultTest/DeployAssert/CheckBootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnRule","version":"0.0.0"}}}}}}}},"Tree":{"id":"Tree","path":"Tree","constructInfo":{"fqn":"constructs.Construct","version":"10.4.2"}}}}} \ No newline at end of file +{"version":"tree-0.1","tree":{"id":"App","path":"","constructInfo":{"fqn":"aws-cdk-lib.App","version":"0.0.0"},"children":{"CompositeAlarmIntegrationTest":{"id":"CompositeAlarmIntegrationTest","path":"CompositeAlarmIntegrationTest","constructInfo":{"fqn":"aws-cdk-lib.Stack","version":"0.0.0"},"children":{"Alarm1":{"id":"Alarm1","path":"CompositeAlarmIntegrationTest/Alarm1","constructInfo":{"fqn":"aws-cdk-lib.aws_cloudwatch.Alarm","version":"0.0.0","metadata":[{"metric":{"warnings":"*","warningsV2":"*"},"threshold":"*","evaluationPeriods":"*"}]},"children":{"Resource":{"id":"Resource","path":"CompositeAlarmIntegrationTest/Alarm1/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_cloudwatch.CfnAlarm","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::CloudWatch::Alarm","aws:cdk:cloudformation:props":{"comparisonOperator":"GreaterThanOrEqualToThreshold","evaluationPeriods":3,"metricName":"Metric","namespace":"CDK/Test","period":300,"statistic":"Average","threshold":100}}}}},"Alarm2":{"id":"Alarm2","path":"CompositeAlarmIntegrationTest/Alarm2","constructInfo":{"fqn":"aws-cdk-lib.aws_cloudwatch.Alarm","version":"0.0.0","metadata":[{"metric":{"warnings":"*","warningsV2":"*"},"threshold":"*","evaluationPeriods":"*"}]},"children":{"Resource":{"id":"Resource","path":"CompositeAlarmIntegrationTest/Alarm2/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_cloudwatch.CfnAlarm","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::CloudWatch::Alarm","aws:cdk:cloudformation:props":{"comparisonOperator":"GreaterThanOrEqualToThreshold","evaluationPeriods":3,"metricName":"Metric","namespace":"CDK/Test","period":300,"statistic":"Average","threshold":1000}}}}},"Alarm3":{"id":"Alarm3","path":"CompositeAlarmIntegrationTest/Alarm3","constructInfo":{"fqn":"aws-cdk-lib.aws_cloudwatch.Alarm","version":"0.0.0","metadata":[{"metric":{"warnings":"*","warningsV2":"*"},"threshold":"*","evaluationPeriods":"*"}]},"children":{"Resource":{"id":"Resource","path":"CompositeAlarmIntegrationTest/Alarm3/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_cloudwatch.CfnAlarm","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::CloudWatch::Alarm","aws:cdk:cloudformation:props":{"comparisonOperator":"GreaterThanOrEqualToThreshold","evaluationPeriods":3,"metricName":"Metric","namespace":"CDK/Test","period":300,"statistic":"Average","threshold":10000}}}}},"Alarm4":{"id":"Alarm4","path":"CompositeAlarmIntegrationTest/Alarm4","constructInfo":{"fqn":"aws-cdk-lib.aws_cloudwatch.Alarm","version":"0.0.0","metadata":[{"metric":{"warnings":"*","warningsV2":"*"},"threshold":"*","evaluationPeriods":"*"}]},"children":{"Resource":{"id":"Resource","path":"CompositeAlarmIntegrationTest/Alarm4/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_cloudwatch.CfnAlarm","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::CloudWatch::Alarm","aws:cdk:cloudformation:props":{"comparisonOperator":"GreaterThanOrEqualToThreshold","evaluationPeriods":3,"metricName":"Metric","namespace":"CDK/Test","period":300,"statistic":"Average","threshold":100000}}}}},"Alarm5":{"id":"Alarm5","path":"CompositeAlarmIntegrationTest/Alarm5","constructInfo":{"fqn":"aws-cdk-lib.aws_cloudwatch.Alarm","version":"0.0.0","metadata":[{"alarmName":"*","metric":{"warnings":"*","warningsV2":"*"},"threshold":"*","evaluationPeriods":"*"}]},"children":{"Resource":{"id":"Resource","path":"CompositeAlarmIntegrationTest/Alarm5/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_cloudwatch.CfnAlarm","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::CloudWatch::Alarm","aws:cdk:cloudformation:props":{"alarmName":"Alarm with space in name","comparisonOperator":"GreaterThanOrEqualToThreshold","evaluationPeriods":3,"metricName":"Metric","namespace":"CDK/Test","period":300,"statistic":"Average","threshold":100000}}}}},"CompositeAlarm":{"id":"CompositeAlarm","path":"CompositeAlarmIntegrationTest/CompositeAlarm","constructInfo":{"fqn":"aws-cdk-lib.aws_cloudwatch.CompositeAlarm","version":"0.0.0","metadata":[{"alarmRule":"*","actionsSuppressor":"*"}]},"children":{"Resource":{"id":"Resource","path":"CompositeAlarmIntegrationTest/CompositeAlarm/Resource","constructInfo":{"fqn":"aws-cdk-lib.aws_cloudwatch.CfnCompositeAlarm","version":"0.0.0"},"attributes":{"aws:cdk:cloudformation:type":"AWS::CloudWatch::CompositeAlarm","aws:cdk:cloudformation:props":{"actionsSuppressor":{"Fn::GetAtt":["Alarm548383B2F","Arn"]},"actionsSuppressorExtensionPeriod":60,"actionsSuppressorWaitPeriod":60,"alarmName":"CompositeAlarmIntegrationTestCompositeAlarm742D2FBA","alarmRule":{"Fn::Join":["",["(((ALARM(\"",{"Fn::GetAtt":["Alarm1F9009D71","Arn"]},"\") OR OK(\"",{"Fn::GetAtt":["Alarm2A7122E13","Arn"]},"\") OR ALARM(\"",{"Fn::GetAtt":["Alarm32341D8D9","Arn"]},"\") OR ALARM(\"",{"Fn::GetAtt":["Alarm548383B2F","Arn"]},"\")) AND (NOT (INSUFFICIENT_DATA(\"",{"Fn::GetAtt":["Alarm4671832C8","Arn"]},"\"))) AND AT_LEAST(2, ALARM, (",{"Fn::GetAtt":["Alarm1F9009D71","Arn"]},", ",{"Fn::GetAtt":["Alarm2A7122E13","Arn"]},", ",{"Fn::GetAtt":["Alarm32341D8D9","Arn"]},")) AND AT_LEAST(60%, NOT OK, (",{"Fn::GetAtt":["Alarm1F9009D71","Arn"]},", ",{"Fn::GetAtt":["Alarm2A7122E13","Arn"]},", ",{"Fn::GetAtt":["Alarm32341D8D9","Arn"]},"))) OR FALSE)"]]}}}}}},"BootstrapVersion":{"id":"BootstrapVersion","path":"CompositeAlarmIntegrationTest/BootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnParameter","version":"0.0.0"}},"CheckBootstrapVersion":{"id":"CheckBootstrapVersion","path":"CompositeAlarmIntegrationTest/CheckBootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnRule","version":"0.0.0"}}}},"CompositeAlarmImportIntegrationTest":{"id":"CompositeAlarmImportIntegrationTest","path":"CompositeAlarmImportIntegrationTest","constructInfo":{"fqn":"aws-cdk-lib.Stack","version":"0.0.0"},"children":{"alarm":{"id":"alarm","path":"CompositeAlarmImportIntegrationTest/alarm","constructInfo":{"fqn":"aws-cdk-lib.aws_cloudwatch.AlarmBase","version":"0.0.0","metadata":[]}},"AlarmName":{"id":"AlarmName","path":"CompositeAlarmImportIntegrationTest/AlarmName","constructInfo":{"fqn":"aws-cdk-lib.CfnOutput","version":"0.0.0"}},"AlarmArn":{"id":"AlarmArn","path":"CompositeAlarmImportIntegrationTest/AlarmArn","constructInfo":{"fqn":"aws-cdk-lib.CfnOutput","version":"0.0.0"}},"BootstrapVersion":{"id":"BootstrapVersion","path":"CompositeAlarmImportIntegrationTest/BootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnParameter","version":"0.0.0"}},"CheckBootstrapVersion":{"id":"CheckBootstrapVersion","path":"CompositeAlarmImportIntegrationTest/CheckBootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnRule","version":"0.0.0"}}}},"cdk-integ-composite-alarm":{"id":"cdk-integ-composite-alarm","path":"cdk-integ-composite-alarm","constructInfo":{"fqn":"@aws-cdk/integ-tests-alpha.IntegTest","version":"0.0.0"},"children":{"DefaultTest":{"id":"DefaultTest","path":"cdk-integ-composite-alarm/DefaultTest","constructInfo":{"fqn":"@aws-cdk/integ-tests-alpha.IntegTestCase","version":"0.0.0"},"children":{"Default":{"id":"Default","path":"cdk-integ-composite-alarm/DefaultTest/Default","constructInfo":{"fqn":"constructs.Construct","version":"10.4.2"}},"DeployAssert":{"id":"DeployAssert","path":"cdk-integ-composite-alarm/DefaultTest/DeployAssert","constructInfo":{"fqn":"aws-cdk-lib.Stack","version":"0.0.0"},"children":{"BootstrapVersion":{"id":"BootstrapVersion","path":"cdk-integ-composite-alarm/DefaultTest/DeployAssert/BootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnParameter","version":"0.0.0"}},"CheckBootstrapVersion":{"id":"CheckBootstrapVersion","path":"cdk-integ-composite-alarm/DefaultTest/DeployAssert/CheckBootstrapVersion","constructInfo":{"fqn":"aws-cdk-lib.CfnRule","version":"0.0.0"}}}}}}}},"Tree":{"id":"Tree","path":"Tree","constructInfo":{"fqn":"constructs.Construct","version":"10.4.2"}}}}} \ No newline at end of file diff --git a/packages/aws-cdk-lib/aws-cloudwatch/test/composite-alarm.test.ts b/packages/aws-cdk-lib/aws-cloudwatch/test/composite-alarm.test.ts index 6f1c5cd960a10..75d31e36ecec4 100644 --- a/packages/aws-cdk-lib/aws-cloudwatch/test/composite-alarm.test.ts +++ b/packages/aws-cdk-lib/aws-cloudwatch/test/composite-alarm.test.ts @@ -108,13 +108,13 @@ describe('CompositeAlarm', () => { 'Arn', ], }, - '"))) AND AT_LEAST(2, ALARM , (', + '"))) AND AT_LEAST(2, ALARM, (', { 'Fn::GetAtt': ['Alarm1F9009D71', 'Arn'] }, ', ', { 'Fn::GetAtt': ['Alarm2A7122E13', 'Arn'] }, ', ', { 'Fn::GetAtt': ['Alarm32341D8D9', 'Arn'] }, - ')) AND AT_LEAST(60%, NOT OK , (', + ')) AND AT_LEAST(60%, NOT OK, (', { 'Fn::GetAtt': ['Alarm1F9009D71', 'Arn'] }, ', ', { 'Fn::GetAtt': ['Alarm2A7122E13', 'Arn'] }, From 5a0ce04e796e7bff51a10a3ef016d74803a074f1 Mon Sep 17 00:00:00 2001 From: aki-kii Date: Thu, 20 Nov 2025 21:41:09 +0900 Subject: [PATCH 5/9] refactor: rewrote AT_LEAST threshold to Union-like class --- .../test/integ.composite-alarm.ts | 10 +- packages/aws-cdk-lib/aws-cloudwatch/README.md | 4 +- .../aws-cloudwatch/lib/alarm-rule.ts | 142 ++++++++++++------ .../test/composite-alarm.test.ts | 41 ++--- 4 files changed, 116 insertions(+), 81 deletions(-) diff --git a/packages/@aws-cdk-testing/framework-integ/test/aws-cloudwatch/test/integ.composite-alarm.ts b/packages/@aws-cdk-testing/framework-integ/test/aws-cloudwatch/test/integ.composite-alarm.ts index b6c30d6f97170..653b55b229675 100644 --- a/packages/@aws-cdk-testing/framework-integ/test/aws-cloudwatch/test/integ.composite-alarm.ts +++ b/packages/@aws-cdk-testing/framework-integ/test/aws-cloudwatch/test/integ.composite-alarm.ts @@ -1,6 +1,6 @@ import { App, CfnOutput, Stack, StackProps } from 'aws-cdk-lib'; import { IntegTest } from '@aws-cdk/integ-tests-alpha'; -import { Alarm, AlarmRule, AlarmState, CompositeAlarm, Metric } from 'aws-cdk-lib/aws-cloudwatch'; +import { Alarm, AlarmRule, AlarmState, AtLeastThreshold, CompositeAlarm, Metric } from 'aws-cdk-lib/aws-cloudwatch'; class CompositeAlarmIntegrationTest extends Stack { constructor(scope: App, id: string, props?: StackProps) { @@ -51,13 +51,13 @@ class CompositeAlarmIntegrationTest extends Stack { alarm5, ), AlarmRule.not(AlarmRule.fromAlarm(alarm4, AlarmState.INSUFFICIENT_DATA)), - AlarmRule.hasAtLeastAlarm({ + AlarmRule.atLeastAlarm({ operands: [alarm1, alarm2, alarm3], - count: 2, + threshold: AtLeastThreshold.count(2), }), - AlarmRule.hasAtLeastNotOk({ + AlarmRule.atLeastNotOk({ operands: [alarm1, alarm2, alarm3], - percentage: 60, + threshold: AtLeastThreshold.percentage(60), }), ), AlarmRule.fromBoolean(false), diff --git a/packages/aws-cdk-lib/aws-cloudwatch/README.md b/packages/aws-cdk-lib/aws-cloudwatch/README.md index 84108eea63aa9..197d855818ad4 100644 --- a/packages/aws-cdk-lib/aws-cloudwatch/README.md +++ b/packages/aws-cdk-lib/aws-cloudwatch/README.md @@ -399,9 +399,9 @@ const alarmRule = cloudwatch.AlarmRule.anyOf( alarm1, cloudwatch.AlarmRule.fromAlarm(alarm2, cloudwatch.AlarmState.OK), alarm3, - cloudwatch.AlarmRule.hasAtLeastAlarm({ + cloudwatch.AlarmRule.atLeastAlarm({ operands: [alarm1, alarm2, alarm3], - count: 1, + threshold: cloudwatch.AtLeastThreshold.count(2), }), ), cloudwatch.AlarmRule.not(cloudwatch.AlarmRule.fromAlarm(alarm4, cloudwatch.AlarmState.INSUFFICIENT_DATA)), diff --git a/packages/aws-cdk-lib/aws-cloudwatch/lib/alarm-rule.ts b/packages/aws-cdk-lib/aws-cloudwatch/lib/alarm-rule.ts index e9528fe404ddb..a41d133974a2b 100644 --- a/packages/aws-cdk-lib/aws-cloudwatch/lib/alarm-rule.ts +++ b/packages/aws-cdk-lib/aws-cloudwatch/lib/alarm-rule.ts @@ -1,5 +1,5 @@ import { IAlarm, IAlarmRule } from './alarm-base'; -import { UnscopedValidationError } from '../../core'; +import { Token, UnscopedValidationError } from '../../core'; /** * Enumeration indicates state of Alarm used in building Alarm Rule. @@ -37,29 +37,95 @@ enum Operator { /** * Options to AT_LEAST AlarmRule wrapper function */ -export interface HasAtLeastOptions { +export interface AtLeastOptions { /** - * function to wrap provided AlarmRule in AT_LEAST expression. + * operands for AT_LEAST expression + * + * can specify an array of CloudWatch Alarms or Alarm Rule expressions */ readonly operands: IAlarm[]; /** - * minimum number of specified alarms - * - * Units: Count + * threshold for AT_LEAST expression * - * @default - Exactly one of `count`, `percentage` is required. + * threshold can be an absolute number or percentage */ - readonly count?: number; + readonly threshold: AtLeastThreshold; +} + +/** + * configuration for creating a threshold for AT_LEAST expression + */ +export interface AtLeastThresholdConfig { /** - * minimum percentage of specified alarms + * threshold of AT_LEAST expression * - * Units: Percentage - * - * @default - Exactly one of `count`, `percentage` is required. + * threshold can be an absolute number or percentage + */ + readonly threshold: string; +} + +/** + * abstract base class for threshold for AT_LEAST expression + */ +export abstract class AtLeastThreshold { + /** + * Creates count threshold configration for AT_LEAST expression + */ + public static count(count: number): AtLeastThresholdCount { + return new AtLeastThresholdCount(count); + } + + /** + * Creates percentage threshold configration for AT_LEAST expression */ - readonly percentage?: number; + public static percentage(percentage: number): AtLeastThresholdPercentage { + return new AtLeastThresholdPercentage(percentage); + } + + /** + * Called when the threshold is initialized to allow this object to bind + */ + public abstract bind(_operands: IAlarm[]): AtLeastThresholdConfig; +} + +/** + * count threshold for AT_LEAST expression + */ +export class AtLeastThresholdCount extends AtLeastThreshold { + constructor(private readonly count: number) { + super(); + } + + bind(_operands: IAlarm[]): AtLeastThresholdConfig { + if (this.count !== undefined && !Token.isUnresolved(this.count) + && (this.count < 1 || _operands.length < this.count || !Number.isInteger(this.count))) { + throw new UnscopedValidationError(`count must be between 1 and alarm length(${_operands.length}) integer, got ${this.count}`); + } + return { + threshold: `${this.count}`, + }; + } +} + +/** + * percentage threshold for AT_LEAST expression + */ +export class AtLeastThresholdPercentage extends AtLeastThreshold { + constructor(private readonly percentage: number) { + super(); + } + + bind(_operands: IAlarm[]): AtLeastThresholdConfig { + if (this.percentage !== undefined && !Token.isUnresolved(this.percentage) + && (this.percentage < 1 || 100 < this.percentage || !Number.isInteger(this.percentage))) { + throw new UnscopedValidationError(`percentage must be between 1 and 100, got ${this.percentage}`); + } + return { + threshold: `${this.percentage}%`, + }; + } } /** @@ -102,9 +168,9 @@ export class AlarmRule { * * @param options options for creating a new AlarmRule. */ - public static hasAtLeastAlarm(options: HasAtLeastOptions): IAlarmRule { + public static atLeastAlarm(options: AtLeastOptions): IAlarmRule { const alarmState = `${AlarmState.ALARM}`; - return this.hasAtLeast(alarmState, options); + return this.atLeast(alarmState, options); } /** @@ -112,9 +178,9 @@ export class AlarmRule { * * @param options options for creating a new AlarmRule. */ - public static hasAtLeastOk(options: HasAtLeastOptions): IAlarmRule { + public static atLeastOk(options: AtLeastOptions): IAlarmRule { const alarmState = `${AlarmState.OK}`; - return this.hasAtLeast(alarmState, options); + return this.atLeast(alarmState, options); } /** @@ -122,9 +188,9 @@ export class AlarmRule { * * @param options options for creating a new AlarmRule. */ - public static hasAtLeastInsufficient(options: HasAtLeastOptions): IAlarmRule { + public static atLeastInsufficient(options: AtLeastOptions): IAlarmRule { const alarmState = `${AlarmState.INSUFFICIENT_DATA}`; - return this.hasAtLeast(alarmState, options); + return this.atLeast(alarmState, options); } /** @@ -132,9 +198,9 @@ export class AlarmRule { * * @param options options for creating a new AlarmRule. */ - public static hasAtLeastNotAlarm(options: HasAtLeastOptions): IAlarmRule { + public static atLeastNotAlarm(options: AtLeastOptions): IAlarmRule { const alarmState = `${Operator.NOT} ${AlarmState.ALARM}`; - return this.hasAtLeast(alarmState, options); + return this.atLeast(alarmState, options); } /** @@ -142,9 +208,10 @@ export class AlarmRule { * * @param options options for creating a new AlarmRule. */ - public static hasAtLeastNotOk(options: HasAtLeastOptions): IAlarmRule { + public static atLeastNotOk(options: AtLeastOptions): IAlarmRule { + options; const alarmState = `${Operator.NOT} ${AlarmState.OK}`; - return this.hasAtLeast(alarmState, options); + return this.atLeast(alarmState, options); } /** @@ -152,9 +219,9 @@ export class AlarmRule { * * @param options options for creating a new AlarmRule. */ - public static hasAtLeastNotInsufficient(options: HasAtLeastOptions): IAlarmRule { + public static atLeastNotInsufficient(options: AtLeastOptions): IAlarmRule { const alarmState = `${Operator.NOT} ${AlarmState.INSUFFICIENT_DATA}`; - return this.hasAtLeast(alarmState, options); + return this.atLeast(alarmState, options); } /** @@ -212,32 +279,19 @@ export class AlarmRule { }; } - private static hasAtLeast(alarmState: string, options: HasAtLeastOptions): IAlarmRule { + private static atLeast(alarmState: string, props: AtLeastOptions): IAlarmRule { return new class implements IAlarmRule { public renderAlarmRule(): string { - if (options.operands.length === 0) { + if (props.operands.length === 0) { throw new UnscopedValidationError(`Did not detect any operands for AT_LEAST ${alarmState}`); } - if ((options.count !== undefined) === (options.percentage !== undefined)) { - throw new UnscopedValidationError('Specify exactly one of \'count\' and \'percentage\''); - } - - if (options.count !== undefined && (options.count < 1 || options.operands.length < options.count - || !Number.isInteger(options.count))) { - throw new UnscopedValidationError(`count must be between 1 and alarm length(${options.operands.length}) integer, got ${options.count}`); - } - - if (options.percentage !== undefined && (options.percentage < 1 - || 100 < options.percentage || !Number.isInteger(options.percentage))) { - throw new UnscopedValidationError(`percentage must be between 1 and 100, got ${options.percentage}`); - } - - const threshold = options.count || `${options.percentage}%`; - const concatAlarms = options.operands + const thresholdOptions = props.threshold.bind(props.operands); + const concatAlarms = props.operands .map(operand => `${operand.alarmArn}`) .join(', '); - return `AT_LEAST(${threshold}, ${alarmState}, (${concatAlarms}))`; + + return `AT_LEAST(${thresholdOptions.threshold}, ${alarmState}, (${concatAlarms}))`; } }; } diff --git a/packages/aws-cdk-lib/aws-cloudwatch/test/composite-alarm.test.ts b/packages/aws-cdk-lib/aws-cloudwatch/test/composite-alarm.test.ts index 75d31e36ecec4..d4b52ad2234b9 100644 --- a/packages/aws-cdk-lib/aws-cloudwatch/test/composite-alarm.test.ts +++ b/packages/aws-cdk-lib/aws-cloudwatch/test/composite-alarm.test.ts @@ -1,6 +1,6 @@ import { Template } from '../../assertions'; import { Duration, Stack } from '../../core'; -import { Alarm, AlarmRule, AlarmState, CompositeAlarm, Metric } from '../lib'; +import { Alarm, AlarmRule, AlarmState, AtLeastThreshold, CompositeAlarm, Metric } from '../lib'; describe('CompositeAlarm', () => { test('test alarm rule expression builder', () => { @@ -51,13 +51,13 @@ describe('CompositeAlarm', () => { alarm5, ), AlarmRule.not(AlarmRule.fromAlarm(alarm4, AlarmState.INSUFFICIENT_DATA)), - AlarmRule.hasAtLeastAlarm({ + AlarmRule.atLeastAlarm({ operands: [alarm1, alarm2, alarm3], - count: 2, + threshold: AtLeastThreshold.count(2), }), - AlarmRule.hasAtLeastNotOk({ + AlarmRule.atLeastNotOk({ operands: [alarm1, alarm2, alarm3], - percentage: 60, + threshold: AtLeastThreshold.percentage(60), }), ), AlarmRule.fromBoolean(false), @@ -237,13 +237,13 @@ describe('CompositeAlarm', () => { })).toThrow('Did not detect any operands for AlarmRule.allOf'); }); - test('empty operands for hasAtLeast', () => { + test('empty operands for atLeast', () => { expect(() => new CompositeAlarm(new Stack(), 'alarm', { - alarmRule: AlarmRule.hasAtLeastAlarm({ operands: [], percentage: 1 }), + alarmRule: AlarmRule.atLeastAlarm({ operands: [], threshold: AtLeastThreshold.count(1) }), })).toThrow('Did not detect any operands for AT_LEAST ALARM'); }); - test.each([{ count: 1, percentage: 1 }, {}])('specify exactly property for hasAtLeast: %s', (threshold) => { + test.each([0, 3, 1.5])('count of atLeast: %s', (count: number) => { const stack = new Stack(); const testMetric = new Metric({ @@ -258,30 +258,11 @@ describe('CompositeAlarm', () => { }); expect(() => new CompositeAlarm(new Stack(), 'alarm', { - alarmRule: AlarmRule.hasAtLeastInsufficient({ operands: [alarm], ...threshold }), - })).toThrow('Specify exactly one of \'count\' and \'percentage\''); - }); - - test.each([0, 3, 1.5])('count of hasAtLeast: %s', (count: number) => { - const stack = new Stack(); - - const testMetric = new Metric({ - namespace: 'CDK/Test', - metricName: 'Metric', - }); - - const alarm = new Alarm(stack, 'Alarm1', { - metric: testMetric, - threshold: 100, - evaluationPeriods: 3, - }); - - expect(() => new CompositeAlarm(new Stack(), 'alarm', { - alarmRule: AlarmRule.hasAtLeastOk({ operands: [alarm], count }), + alarmRule: AlarmRule.atLeastOk({ operands: [alarm], threshold: AtLeastThreshold.count(count) }), })).toThrow(`count must be between 1 and alarm length(1) integer, got ${count}`); }); - test.each([0, 101, 1.5])('percentage of hasAtLeast: %s%', (percentage: number) => { + test.each([0, 101, 1.5])('percentage of atLeast: %s%', (percentage: number) => { const stack = new Stack(); const testMetric = new Metric({ @@ -296,7 +277,7 @@ describe('CompositeAlarm', () => { }); expect(() => new CompositeAlarm(new Stack(), 'alarm', { - alarmRule: AlarmRule.hasAtLeastOk({ operands: [alarm], percentage }), + alarmRule: AlarmRule.atLeastOk({ operands: [alarm], threshold: AtLeastThreshold.percentage(percentage) }), })).toThrow(`percentage must be between 1 and 100, got ${percentage}`); }); }); From c1d81e730f362cfe653cb8c130605a424f0cd63c Mon Sep 17 00:00:00 2001 From: aki-kii <65025532+aki-kii@users.noreply.github.com> Date: Sat, 22 Nov 2025 09:31:34 +0900 Subject: [PATCH 6/9] Update packages/aws-cdk-lib/aws-cloudwatch/lib/alarm-rule.ts Co-authored-by: Kenta Goto <24818752+go-to-k@users.noreply.github.com> --- packages/aws-cdk-lib/aws-cloudwatch/lib/alarm-rule.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/aws-cdk-lib/aws-cloudwatch/lib/alarm-rule.ts b/packages/aws-cdk-lib/aws-cloudwatch/lib/alarm-rule.ts index a41d133974a2b..b796374591ba5 100644 --- a/packages/aws-cdk-lib/aws-cloudwatch/lib/alarm-rule.ts +++ b/packages/aws-cdk-lib/aws-cloudwatch/lib/alarm-rule.ts @@ -35,7 +35,7 @@ enum Operator { } /** - * Options to AT_LEAST AlarmRule wrapper function + * Options for AT_LEAST AlarmRule wrapper function */ export interface AtLeastOptions { From 67d792c29520490aedf44975c7e3005825eca603 Mon Sep 17 00:00:00 2001 From: aki-kii <65025532+aki-kii@users.noreply.github.com> Date: Sat, 22 Nov 2025 09:31:48 +0900 Subject: [PATCH 7/9] Update packages/aws-cdk-lib/aws-cloudwatch/lib/alarm-rule.ts Co-authored-by: Kenta Goto <24818752+go-to-k@users.noreply.github.com> --- packages/aws-cdk-lib/aws-cloudwatch/lib/alarm-rule.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/aws-cdk-lib/aws-cloudwatch/lib/alarm-rule.ts b/packages/aws-cdk-lib/aws-cloudwatch/lib/alarm-rule.ts index b796374591ba5..68a78b975ed68 100644 --- a/packages/aws-cdk-lib/aws-cloudwatch/lib/alarm-rule.ts +++ b/packages/aws-cdk-lib/aws-cloudwatch/lib/alarm-rule.ts @@ -86,8 +86,10 @@ export abstract class AtLeastThreshold { /** * Called when the threshold is initialized to allow this object to bind + * + * @internal */ - public abstract bind(_operands: IAlarm[]): AtLeastThresholdConfig; + public abstract _bind(operands: IAlarm[]): AtLeastThresholdConfig; } /** From 2059bf890997c6bc9c360846519e2e997f28af30 Mon Sep 17 00:00:00 2001 From: aki-kii Date: Sat, 22 Nov 2025 10:26:00 +0900 Subject: [PATCH 8/9] refactor: minor adjustments to method names and parameter names --- .../aws-cdk-lib/aws-cloudwatch/lib/alarm-rule.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/aws-cdk-lib/aws-cloudwatch/lib/alarm-rule.ts b/packages/aws-cdk-lib/aws-cloudwatch/lib/alarm-rule.ts index 68a78b975ed68..3a96c57917565 100644 --- a/packages/aws-cdk-lib/aws-cloudwatch/lib/alarm-rule.ts +++ b/packages/aws-cdk-lib/aws-cloudwatch/lib/alarm-rule.ts @@ -57,7 +57,7 @@ export interface AtLeastOptions { /** * configuration for creating a threshold for AT_LEAST expression */ -export interface AtLeastThresholdConfig { +interface AtLeastThresholdConfig { /** * threshold of AT_LEAST expression * @@ -100,10 +100,10 @@ export class AtLeastThresholdCount extends AtLeastThreshold { super(); } - bind(_operands: IAlarm[]): AtLeastThresholdConfig { + _bind(operands: IAlarm[]): AtLeastThresholdConfig { if (this.count !== undefined && !Token.isUnresolved(this.count) - && (this.count < 1 || _operands.length < this.count || !Number.isInteger(this.count))) { - throw new UnscopedValidationError(`count must be between 1 and alarm length(${_operands.length}) integer, got ${this.count}`); + && (this.count < 1 || operands.length < this.count || !Number.isInteger(this.count))) { + throw new UnscopedValidationError(`count must be between 1 and alarm length(${operands.length}) integer, got ${this.count}`); } return { threshold: `${this.count}`, @@ -119,7 +119,7 @@ export class AtLeastThresholdPercentage extends AtLeastThreshold { super(); } - bind(_operands: IAlarm[]): AtLeastThresholdConfig { + _bind(_operands: IAlarm[]): AtLeastThresholdConfig { if (this.percentage !== undefined && !Token.isUnresolved(this.percentage) && (this.percentage < 1 || 100 < this.percentage || !Number.isInteger(this.percentage))) { throw new UnscopedValidationError(`percentage must be between 1 and 100, got ${this.percentage}`); @@ -288,12 +288,12 @@ export class AlarmRule { throw new UnscopedValidationError(`Did not detect any operands for AT_LEAST ${alarmState}`); } - const thresholdOptions = props.threshold.bind(props.operands); + const thresholdConfig = props.threshold._bind(props.operands); const concatAlarms = props.operands .map(operand => `${operand.alarmArn}`) .join(', '); - return `AT_LEAST(${thresholdOptions.threshold}, ${alarmState}, (${concatAlarms}))`; + return `AT_LEAST(${thresholdConfig.threshold}, ${alarmState}, (${concatAlarms}))`; } }; } From dad45b8ec22cd18168db3dc764c8c66f43dbd038 Mon Sep 17 00:00:00 2001 From: aki-kii Date: Sat, 22 Nov 2025 11:33:09 +0900 Subject: [PATCH 9/9] fix: add @internal comment for _bind method --- packages/aws-cdk-lib/aws-cloudwatch/lib/alarm-rule.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/aws-cdk-lib/aws-cloudwatch/lib/alarm-rule.ts b/packages/aws-cdk-lib/aws-cloudwatch/lib/alarm-rule.ts index 3a96c57917565..e4105dd948a1a 100644 --- a/packages/aws-cdk-lib/aws-cloudwatch/lib/alarm-rule.ts +++ b/packages/aws-cdk-lib/aws-cloudwatch/lib/alarm-rule.ts @@ -100,6 +100,11 @@ export class AtLeastThresholdCount extends AtLeastThreshold { super(); } + /** + * Called when the threshold is initialized to allow this object to bind + * + * @internal + */ _bind(operands: IAlarm[]): AtLeastThresholdConfig { if (this.count !== undefined && !Token.isUnresolved(this.count) && (this.count < 1 || operands.length < this.count || !Number.isInteger(this.count))) { @@ -118,7 +123,11 @@ export class AtLeastThresholdPercentage extends AtLeastThreshold { constructor(private readonly percentage: number) { super(); } - + /** + * Called when the threshold is initialized to allow this object to bind + * + * @internal + */ _bind(_operands: IAlarm[]): AtLeastThresholdConfig { if (this.percentage !== undefined && !Token.isUnresolved(this.percentage) && (this.percentage < 1 || 100 < this.percentage || !Number.isInteger(this.percentage))) {