-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Description
Describe the bug
The issue arises when associating multiple LambdaAction instances with a single Lambda function and different CloudWatch alarms. AWS CDK automatically creates a permission (AlarmPermission) for each alarm action associated with the Lambda function. However, if multiple alarms are associated with the same Lambda function, CDK attempts to create the same permission (e.g., AlarmPermission) multiple times, causing a conflict.
Regression Issue
- Select this option if this issue appears to be a regression.
Last Known Working CDK Version
No response
Expected Behavior
AWS CDK should add unique permissions for each LambdaAction associated with the Lambda function when multiple CloudWatch alarms are configured to invoke the same Lambda.
Current Behavior
error output
jsii.errors.JavaScriptError:
@jsii/kernel.RuntimeError: Error: There is already a Construct with name 'AlarmPermission' in Function [JenkinsBitbucketLambdaFunction]
at Kernel._Kernel_ensureSync (/tmp/tmp6m2tuagq/lib/program.js:9510:23)
at Kernel.invoke (/tmp/tmp6m2tuagq/lib/program.js:8874:102)
at KernelHost.processRequest (/tmp/tmp6m2tuagq/lib/program.js:10715:36)
at KernelHost.run (/tmp/tmp6m2tuagq/lib/program.js:10675:22)
at Immediate._onImmediate (/tmp/tmp6m2tuagq/lib/program.js:10676:46)
at process.processImmediate (node:internal/timers:478:21)
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/path/to/repo/app.py", line 16, in <module>
jenkins_bitbucket_stack = JenkinsBitbucketMonitoringStack(app, "Jenkins-Bitbucket-Monitoring-Stack", env=aws_env)
File "/path/to/repo/.venv/lib/python3.10/site-packages/jsii/_runtime.py", line 118, in __call__
inst = super(JSIIMeta, cast(JSIIMeta, cls)).__call__(*args, **kwargs)
File "/path/to/repo/jenkins_bitbucket_stack.py", line 166, in __init__
alarm_artifactory.add_alarm_action(cloudwatch_actions.LambdaAction(lambda_function))
File "/path/to/repo/.venv/lib/python3.10/site-packages/aws_cdk/aws_cloudwatch/__init__.py", line 14809, in add_alarm_action
return typing.cast(None, jsii.invoke(self, "addAlarmAction", [*actions]))
File "/path/to/repo/.venv/lib/python3.10/site-packages/jsii/_kernel/__init__.py", line 149, in wrapped
return _recursize_dereference(kernel, fn(kernel, *args, **kwargs))
File "/path/to/repo/.venv/lib/python3.10/site-packages/jsii/_kernel/__init__.py", line 399, in invoke
response = self.provider.invoke(
File "/path/to/repo/.venv/lib/python3.10/site-packages/jsii/_kernel/providers/process.py", line 380, in invoke
return self._process.send(request, InvokeResponse)
File "/path/to/repo/.venv/lib/python3.10/site-packages/jsii/_kernel/providers/process.py", line 342, in send
raise RuntimeError(resp.error) from JavaScriptError(resp.stack)
RuntimeError: Error: There is already a Construct with name 'AlarmPermission' in Function [JenkinsBitbucketLambdaFunction]
Reproduction Steps
CDK code to reproduce issue
from aws_cdk import (
Stack,
aws_cloudwatch as cloudwatch,
aws_lambda as _lambda,
aws_cloudwatch_actions as cloudwatch_actions,
Duration
)
from constructs import Construct
class GenericMonitoringStack(Stack):
def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
super().__init__(scope, construct_id, **kwargs)
# Lambda function
lambda_function = _lambda.Function(self, "GenericLambdaFunction",
runtime=_lambda.Runtime.PYTHON_3_12,
role=kwargs.get('lambda_execution_role'), # Pass the execution role as a keyword argument
handler="your_handler.lambda_handler",
timeout=Duration.minutes(5),
function_name="Generic-CloudWatch-AlarmAction",
code=_lambda.Code.from_asset("path/to/your/code"),
environment={
"ALARM_NAME_1": "Generic_Alarm_1",
"ALARM_NAME_2": "Generic_Alarm_2",
"TAG_VALUE_1": "Tag_Value_1",
"TAG_VALUE_2": "Tag_Value_2",
}
)
# CloudWatch Alarm 1 (replace placeholders with actual alarm configuration)
alarm_1 = cloudwatch.Alarm(self, "GenericAlarm1",
alarm_name="Generic_Alarm_1",
metric=kwargs.get('metric_1'), # Pass the metric as a keyword argument
threshold=1,
evaluation_periods=2,
comparison_operator=cloudwatch.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD,
treat_missing_data=cloudwatch.TreatMissingData.NOT_BREACHING
)
# CloudWatch Alarm 2 (replace placeholders with actual alarm configuration)
alarm_2 = cloudwatch.Alarm(self, "GenericAlarm2",
alarm_name="Generic_Alarm_2",
metric=kwargs.get('metric_2'), # Pass the metric as a keyword argument
threshold=1,
evaluation_periods=2,
comparison_operator=cloudwatch.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD,
treat_missing_data=cloudwatch.TreatMissingData.NOT_BREACHING
)
# Adding CloudWatch Alarms with Lambda actions
alarm_1.add_alarm_action(cloudwatch_actions.LambdaAction(lambda_function))
alarm_2.add_alarm_action(cloudwatch_actions.LambdaAction(lambda_function)) # This line can cause the conflict if the permissions are not handled properly
Possible Solution
created 2 lambda aliases and assigning them to the cloud watch actions
from aws_cdk import (
Stack,
aws_lambda as _lambda,
aws_cloudwatch as cloudwatch,
aws_cloudwatch_actions as cloudwatch_actions,
Duration
)
from constructs import Construct
class MonitoringStackWithAliases(Stack):
def __init__(self, scope: Construct, construct_id: str, **kwargs) -> None:
super().__init__(scope, construct_id, **kwargs)
# Lambda function
lambda_function = _lambda.Function(self, "GenericLambdaFunction",
runtime=_lambda.Runtime.PYTHON_3_12,
handler="your_handler.lambda_handler",
timeout=Duration.minutes(5),
code=_lambda.Code.from_asset("path/to/your/code")
)
# Lambda alias for the first alarm
lambda_alias_1 = _lambda.Alias(self, "LambdaAlias1",
alias_name="alias1",
version=lambda_function.current_version
)
# Lambda alias for the second alarm
lambda_alias_2 = _lambda.Alias(self, "LambdaAlias2",
alias_name="alias2",
version=lambda_function.current_version
)
# CloudWatch Alarm 1
alarm_1 = cloudwatch.Alarm(self, "Alarm1",
alarm_name="Alarm1",
metric=kwargs.get('metric_1'), # Pass the metric as a keyword argument
threshold=1,
evaluation_periods=2,
comparison_operator=cloudwatch.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD,
treat_missing_data=cloudwatch.TreatMissingData.NOT_BREACHING
)
# CloudWatch Alarm 2
alarm_2 = cloudwatch.Alarm(self, "Alarm2",
alarm_name="Alarm2",
metric=kwargs.get('metric_2'), # Pass the metric as a keyword argument
threshold=1,
evaluation_periods=2,
comparison_operator=cloudwatch.ComparisonOperator.GREATER_THAN_OR_EQUAL_TO_THRESHOLD,
treat_missing_data=cloudwatch.TreatMissingData.NOT_BREACHING
)
# Use Lambda Alias 1 with Alarm 1
alarm_1.add_alarm_action(cloudwatch_actions.LambdaAction(lambda_alias_1))
# Use Lambda Alias 2 with Alarm 2
alarm_2.add_alarm_action(cloudwatch_actions.LambdaAction(lambda_alias_2))
Additional Information/Context
per this document i should be able to tie the function
https://docs.aws.amazon.com/cdk/api/v2/python/aws_cdk.aws_cloudwatch_actions/README.html
CDK CLI Version
2.151.0
Framework Version
No response
Node.js Version
v21.5.0
OS
Linux tobot-win10 5.15.153.1-microsoft-standard-WSL2 #1 SMP Fri Mar 29 23:14:13 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux
Language
Python
Language Version
3.10.12
Other information
No response