How to set up a lambda function for dynamic cloudwatch event rules

Even longer overdue, but the reason my code above adds the lambda permission in runtime is that I couldn’t find how to set a resource policy on a lambda function statically. Since then I’ve found how that can be done, so the code above can be simplified into:

serverless.yml

provider:
  [...]
  iamRoleStatements:
    - Effect: Allow
      Action:
        - events:putRule
        - events:putTargets
        - events:deleteRule
        - events:removeTargets
[...]
functions:
  MyScheduledFunction:
    [...]
    # Note no events at all, this will be triggered by CW rules created in runtime

resources:
  Resources:
    MyScheduledFunctionResourcePolicy:
      Type: 'AWS::Lambda::Permission'
      Properties:
        FunctionName:
          Fn::GetAtt: [MyScheduledFunctionLambdaFunction, Arn]
       Action: 'lambda:invokeFunction'
       Principal: 'events.amazonaws.com'
       SourceArn:
         Fn::Sub: 'arn:aws:events:${AWS::Region}:${AWS::AccountId}:rule/*'

scheduler.js

const functionArn = <my-scheduled-function-arn>;
const ruleName = <rule-name>;

// Add CW rule
await cwe.putRule({
  Name: ruleName,
  ScheduleExpression: '<cron expression>',
}).promise();

// Tell CW rule to invoke function
await cwe.putTargets({
  Rule: ruleName,
  Targets: [
    {
      Id: '<my-target-id>',
      Arn: functionArn,
      Input: JSON.stringify({
        rule: {name: ruleName},
        data: { ... }
      })
    }
  ]
}).promise();

To avoid adding lots and lots of CW rules, the invoked scheduled function may clean up by removing the rule that triggered it:

my-scheduled-function.js

exports.handler = async event => {
  const ruleName = event.rule.name;

  try {
    [...]
  } finally {
    await cwe.removeTargets({
      Rule: ruleName,
      Ids: ['<my-target-id>']
    }).promise();

    await cwe.deleteRule({
      Name: ruleName
    }).promise();
  }
}
2 Likes