Skip to content

lambda: associating multiple LambdaAction instances with a single Lambda function and different CloudWatch alarms. #31754

@terryobot

Description

@terryobot

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

Metadata

Metadata

Assignees

Labels

@aws-cdk/aws-lambdaRelated to AWS LambdabugThis issue is a bug.closed-for-stalenessThis issue was automatically closed because it hadn't received any attention in a while.p2response-requestedWaiting on additional info and feedback. Will move to "closing-soon" in 7 days.

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions