Home > Enterprise >  Why is my CloudWatch rule triggered but not invoking my Lambda function?
Why is my CloudWatch rule triggered but not invoking my Lambda function?

Time:05-23

I have created a Lambda function using the CDK.

In my Java application, I am programmatically creating a CloudWatch Event Rule with a target pointing to the function via its ARN.

The CloudWatch rule gets triggered but it fails to invoke the Lambda. When I manually add the CloudWatch rule as a trigger for the Lambda from AWS Console, the function does get invoked.

CDK code

private createPregameEventsLambda(props: InfraStackProps) {
        const lambdaEnvVars = {
            'Domain': props.stage,
            'Region': props.region,
            'ResourceNamePrefix': props.resourceNamePrefix
        };

        const pregameEventsLambda = new Lambda(this, "PregameEvents-Lambda", {
            ...props,
            name: LambdaConfig.PREGAME_EVENTS_LAMBDA.name,
            handler: LambdaConfig.PREGAME_EVENTS_LAMBDA.handler,
            environment: {
                variables: lambdaEnvVars
            },
            role: this.lambdaExecutionRole
        }).getFunction()
}

Java application:

public void createCloudWatchRule(final LiveEventIdRelationInfo info) {
        final String smpId = info.getSmpId();
        final Date date = new Date(startTime);
        final Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);

        calendar.setTimeZone(TimeZone.getTimeZone("GMT 0"));
        final String cronExpression =  getCronExpression(calendar);

        final PutRuleRequest putRuleRequest = new PutRuleRequest()
                .withScheduleExpression(cronExpression)
                .withName(ruleName);
        cloudWatchEventClient.putRule(putRuleRequest);

        final Target target = new Target()
                .withId(LAMBDA_TARGET_ID)
                .withArn(functionArn)
                .withInput(jsonString);
        final PutTargetsRequest putTargetsRequest = new PutTargetsRequest()
                .withRule(ruleName)
                .withTargets(target);
        cloudWatchEventClient.putTargets(putTargetsRequest);
    }

Console shows that the Rule created has the correct Lambda ARN but it still does not get invoked.

Is there an InvokeFunction permission I need to add to the CDK code?

Am I missing permissions?

CodePudding user response:

Is there an InvokeFunction permission I need to add to the CDK code?

No, this isn't related to your CDK code, since you're creating a new EventBridge (CloudWatch) rule outside of your CDK code. You're not creating the rule using the CDK so it's not the right place to look.

Your Lambda function is missing a resource-based IAM policy, which grants EventBridge & specifically your created rule, to invoke it.

Since you're creating the rule & target programmatically, you will also need to grant this permission programmatically using AddPermission & the LambdaClient in your Java application.

Among other things, AddPermission allows you to set a source ARN which is the ARN of the AWS resource that invokes the function. This would be the ARN of the EventBridge rule.

This is not required however if you do not specify the source, do note that other accounts could potentially configure resources in their account to invoke your Lambda function. As such, I'd recommend always setting the source ARN.

Applying the above to your code, cloudWatchEventClient.putRule(putRuleRequest); will actually return the ARN of the created rule in the PutRuleReponse via the ruleArn() method i.e.

final PutRuleResponse response = cloudWatchEventClient.putRule(putRuleRequest);
final String ruleArn = response.ruleArn();

Once you have this value captured, create & send a AddPermissionRequest to grant the necessary permission.

This should work using the AWS SDK for Java 2.x:

public void createCloudWatchRule(final LiveEventIdRelationInfo info) {
    ...
    final PutRuleRequest putRuleRequest = new PutRuleRequest()
            .withScheduleExpression(cronExpression)
            .withName(ruleName);

    final PutRuleResponse response = cloudWatchEventClient.putRule(putRuleRequest);
    final String ruleArn = response.ruleArn();

    final Target target = new Target()
            .withId(LAMBDA_TARGET_ID)
            .withArn(functionArn)
            .withInput(jsonString);
    final PutTargetsRequest putTargetsRequest = new PutTargetsRequest()
            .withRule(ruleName)
            .withTargets(target);
    cloudWatchEventClient.putTargets(putTargetsRequest);

    final AddPermissionRequest addPermissionRequest = new AddPermissionRequest()
        .withFunctionName(functionArn)
        .withStatementId("sid-"   LAMBDA_TARGET_ID)
        .withAction("lambda:InvokeFunction")
        .withPrincipal("events.amazonaws.com")
        .withSourceArn(ruleArn)
        .build();
    
    lambdaClient.addPermission(addPermissionRequest);
}

If you're using AWS SDK for Java 1.x, the same concepts apply - there will be minor syntax differences but cross reference the above links with the v1 docs and you'll be able to easily figure it out.

  • Related