3
votes

I am doing a hands on where I want to add an SNS trigger to a lambda function which then sends a message to a slack channel. There is a blueprint for this lambda in python and also a template test event which looks like the following

{
  "Records": [
    {
      "EventVersion": "1.0",
      "EventSubscriptionArn": "arn:aws:sns:EXAMPLE",
      "EventSource": "aws:sns",
      "Sns": {
        "SignatureVersion": "1",
        "Timestamp": "1970-01-01T00:00:00.000Z",
        "Signature": "EXAMPLE",
        "SigningCertUrl": "EXAMPLE",
        "MessageId": "12345",
        "Message": {
          "AlarmName": "SlackAlarm",
          "NewStateValue": "OK",
          "NewStateReason": "Threshold Crossed: 1 datapoint (0.0) was not greater than or equal to the threshold (1.0)."
        },
        "MessageAttributes": {
          "Test": {
            "Type": "String",
            "Value": "TestString"
          },
          "TestBinary": {
            "Type": "Binary",
            "Value": "TestBinary"
          }
        },
        "Type": "Notification",
        "UnsubscribeUrl": "EXAMPLE",
        "TopicArn": "arn:aws:sns:EXAMPLE",
        "Subject": "TestInvoke"
      }
    }
  ]

The code in lambda handler from the blueprint is as follows

import boto3
import json
import logging
import os

from base64 import b64decode
from urllib.request import Request, urlopen
from urllib.error import URLError, HTTPError

HOOK_URL = os.environ['kmsEncryptedHookUrl']
SLACK_CHANNEL = os.environ['slackChannel']

logger = logging.getLogger()
logger.setLevel(logging.INFO)

def lambda_handler(event, context):
    logger.info("Event: " + str(event))
    message = event['Records'][0]['Sns']['Message']
    logger.info("Message: " + str(message))

    alarm_name = message['AlarmName']
    new_state = message['NewStateValue']
    reason = message['NewStateReason']

    slack_message = {
        'channel': SLACK_CHANNEL,
        'text': "%s state is now %s: %s" % (alarm_name, new_state, reason)
    }

    req = Request(HOOK_URL, json.dumps(slack_message).encode('utf-8'))
    try:
        response = urlopen(req)
        response.read()
        logger.info("Message posted to %s", slack_message['channel'])
    except HTTPError as e:
        logger.error("Request failed: %d %s", e.code, e.reason)
    except URLError as e:
        logger.error("Server connection failed: %s", e.reason)

When I run the test event, the lambda runs successfully.

I wanted to publish a message in SNS topic from the console to see if the lambda is triggered correctly. But when I try to publish the JSON object as a message body, I am getting the error

[ERROR] TypeError: string indices must be integersTraceback (most recent call last):  File "/var/task/lambda_function.py", line 21, in lambda_handler    alarm_name = message['AlarmName']

I tried giving plain json

{
  "AlarmName": "PublishedAlarm",
  "NewStateValue": "OK",
  "NewStateReason": "This alarm is published"
}

I tried giving a stringified JSON

"{\"AlarmName\": \"PublishedAlarm\",\"NewStateValue\": \"OK\",\"NewStateReason\": \"This alarm is published\"}"

I tried choosing Custom payload for each delivery message structure and then gave the following message body

{
  "default": "Sample fallback message",
  "email": "Sample message for email endpoints",
  "sqs": "Sample message for Amazon SQS endpoints",
  "lambda": "{\"AlarmName\": \"PublishedAlarm\",\"NewStateValue\": \"OK\",\"NewStateReason\": \"This alarm is published\"}",
  "http": "Sample message for HTTP endpoints",
  "https": "Sample message for HTTPS endpoints",
  "sms": "Sample message for SMS endpoints",
  "firehose": "Sample message for Amazon Kinesis Data Firehose endpoints",
  "APNS": "{\"aps\":{\"alert\": \"Sample message for iOS endpoints\"} }",
  "APNS_SANDBOX": "{\"aps\":{\"alert\":\"Sample message for iOS development endpoints\"}}",
  "APNS_VOIP": "{\"aps\":{\"alert\":\"Sample message for Apple VoIP endpoints\"}}",
  "APNS_VOIP_SANDBOX": "{\"aps\":{\"alert\": \"Sample message for Apple VoIP development endpoints\"} }",
  "MACOS": "{\"aps\":{\"alert\":\"Sample message for MacOS endpoints\"}}",
  "MACOS_SANDBOX": "{\"aps\":{\"alert\": \"Sample message for MacOS development endpoints\"} }",
  "GCM": "{ \"data\": { \"message\": \"Sample message for Android endpoints\" } }",
  "ADM": "{ \"data\": { \"message\": \"Sample message for FireOS endpoints\" } }",
  "BAIDU": "{\"title\":\"Sample message title\",\"description\":\"Sample message for Baidu endpoints\"}",
  "MPNS": "<?xml version=\"1.0\" encoding=\"utf-8\"?><wp:Notification xmlns:wp=\"WPNotification\"><wp:Tile><wp:Count>ENTER COUNT</wp:Count><wp:Title>Sample message for Windows Phone 7+ endpoints</wp:Title></wp:Tile></wp:Notification>",
  "WNS": "<badge version=\"1\" value=\"42\"/>"
}

Nothing worked. I've also subscribed an email address to the topic and I'm getting emails without any issues.

How can I simulate the test event given in lambda event templates from the SNS?

1
How did it go with the issue? Still persists?Marcin
@Marcin I used json instead of ast.literal_eval as you suggested. But yes, parsing the message in lambda handler worked. Thanks!sruthi

1 Answers

7
votes

When you send your plain json message using SNS, it will be delivered to lambda in in the format:

'Message': '{\n  "AlarmName": "PublishedAlarm",\n  "NewStateValue": "OK",\n  "NewStateReason": "This alarm is published"\n}'

You can parse it using ast' literal_eval method:

import ast

#...
#...

def lambda_handler(event, context):
    logger.info("Event: " + str(event))
    message = event['Records'][0]['Sns']['Message']
    logger.info("Message: " + str(message))

    message = ast.literal_eval(event['Records'][0]['Sns']['Message'])

    alarm_name = message['AlarmName']
    new_state = message['NewStateValue']
    reason = message['NewStateReason']

#...
#...