How to send json message body in aws SNS using console - json

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?

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']
#...
#...

Related

Trying to trigger a SSM:Run Command action when my Cloudwatch alarm enters "ALARM" state

Trying to trigger an SSM:Run Command action when my cloudwatch alarm enters the "ALARM" state. I am trying to achieve this with Cloudwatch Rule - Event pattern and by fetching the AWS Cloud Trail API Logs.
Tried with Monitoring and event name as "DescribeAlarms" and stateValue as "ALARM". Just tried adding my SNS topic (instead of SSM:RunCommand) to verify it triggers an email to me when this enters to ALARM state, but no luck.
```{
"source": [
"aws.monitoring"
],
"detail-type": [
"AWS API Call via CloudTrail"
],
"detail": {
"eventSource": [
"monitoring.amazonaws.com"
],
"eventName": [
"DescribeAlarms"
],
"requestParameters": {
"stateValue": [
"ALARM"
]
}
}
}```
I am expecting when this condition is met, here - any alarm that goes into ALARM state should hit the Target - which is my SNS topic.
UPDATE:
Thanks #John for the clarification. As you suggested, I am trying to go with SNS-> Lambda->SSM Run Command. But I am not able to fetch the instance ID from SNS event. It says [Records - Keyerror]. Read some of your posts and tried all. But not able to get through. Could you please help?
Received event: {
"Records": [
{
"EventSource": "aws:sns",
"EventVersion": "1.0",
"EventSubscriptionArn": "arn:aws:sns:eu-west-1:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
"Sns": {
"Type": "Notification",
"MessageId": "********************c",
"TopicArn": "arn:aws:sns:eu-west-1:*******************************",
"Subject": "ALARM: \"!!! Critical Alert !!! Disk Space is going to be full in Automation Testing\" in EU (Ireland)",
"Message": "{\"AlarmName\":\"!!! Critical Alert !!! Disk Space is going to be full in Automation Testing\",\"AlarmDescription\":\"Disk Space is going to be full in Automation Testing\",\"AWSAccountId\":\"***********\",\"NewStateValue\":\"ALARM\",\"NewStateReason\":\"Threshold Crossed: 1 out of the last 1 datapoints [**********] was less than or equal to the threshold (70.0) (minimum 1 datapoint for OK -> ALARM transition).\",\"StateChangeTime\":\"******************\",\"Region\":\"EU (Ireland)\",\"OldStateValue\":\"OK\",\"Trigger\":{\"MetricName\":\"disk_used_percent\",\"Namespace\":\"CWAgent\",\"StatisticType\":\"Statistic\",\"Statistic\":\"AVERAGE\",\"Unit\":null,\"Dimensions\":[{\"value\":\"/\",\"name\":\"path\"},{\"value\":\"i-****************\",\"name\":\"InstanceId\"},{\"value\":\"ami-****************\",\"name\":\"ImageId\"},{\"value\":\"t2.micro\",\"name\":\"InstanceType\"},{\"value\":\"xvda1\",\"name\":\"device\"},{\"value\":\"xfs\",\"name\":\"fstype\"}],\"Period\":300,\"EvaluationPeriods\":1,\"ComparisonOperator\":\"LessThanOrEqualToThreshold\",\"Threshold\":70.0,\"TreatMissingData\":\"- TreatMissingData: missing\",\"EvaluateLowSampleCountPercentile\":\"\"}}",
"Timestamp": "2019-06-29T19:23:43.829Z",
"SignatureVersion": "1",
"Signature": "XXXXXXXXXXXX",
"SigningCertUrl": "https://sns.eu-west-1.amazonaws.com/XXXXXXXX.pem",
"UnsubscribeUrl": "https://sns.eu-west-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:eu-west-1XXXXXXXXXXXXXXXXXXXXX",
"MessageAttributes":
{}
}
}
]
}
Below is my Lambda function:
from __future__ import print_function
import boto3
import json
ssm = boto3.client('ssm')
ec2 = boto3.resource('ec2')
print('Loading function')
def lambda_handler(event, context):
# Dump the event to the log, for debugging purposes
print("Received event: " + json.dumps(event, indent=2))
message = event['Records']['Sns']['Message']
msg = json.loads(message)
InstanceId = msg['InstanceId']['value']
print ("Instance: %s" % InstanceId)
This probably won't work because AWS CloudTrail only captures API calls to AWS and the movement of a CloudWatch alarm into the ALARM state is an internal change that is not caused by an API call.
I would recommend:
Amazon CloudWatch alarm triggers an AWS Lambda function
The Lambda function calls the SSM Run Command (eg send_command())
Able to achieve with below changes:
from __future__ import print_function
import boto3
import json
ssm = boto3.client('ssm')
ec2 = boto3.resource('ec2')
print('Loading function')
def lambda_handler(event, context):
# Dump the event to the log, for debugging purposes
print("Received event: " + json.dumps(event, indent=2))
message = json.loads(event['Records'][0]['Sns']['Message'])
instance_id = message['Trigger']['Dimensions'][1]['value']
print ("Instance: %s" % instance_id)

Python dictionary converts single slash to double slash

I am trying to send data using requests trying to send the payload from my side which requires date to be formatted like "\/Date(1532010600000)\/"
I am trying to make a custom payload which is to be sent using requests:
payload = {
"GW_MeetingID": "1231324654654",
"AltID": "This is ALT ID",
"MeetingSubject": company,
"MeetingComment": "",
"RoomID": "xxxxx",
"TimeZoneId": "Dateline Standard Time",
"Organizer": organiser,
"Start": "\\/Date(1532010600000)\\/"
}
here the key should be "\/Date(1532010600000)\/" rather than "\\", using extra "\" as escape character. When i am trying to print this payload, it is showing value of start keys with "\\" which the api I am trying to send response to, doesn't accept. It only accepts 'Start' key as "\/Date(1532010600000)\/"
The solution that I have tried are using .encode('utf-8') and then decoding it using .decode('unicode_escape'). But still the dictionary had same response with "\\"
How can I make this payload to contain "\" in Start key?
import json
import requests
payload = {
"GW_MeetingID": "1231324654654",
"AltID": "This is ALT ID",
"MeetingSubject": company,
"MeetingComment": "",
"RoomID": "e3e63148-5e4f-426e-98de-dec1687c9930",
"TimeZoneId": "Dateline Standard Time",
"Organizer": organiser,
"Start": "\\/Date(1532010600000)\\/"
}
print (payload)
# print (json.dumps(payload))
# a = (ast.literal_eval(payload))
headers = {'Content-Type': 'application/json'}
r = requests.post(url, data=json.dumps(payload), headers=headers)
print (r.text)

Can JSON String format be converted to Actual format using groovy?

I have the following JSON String format getting from external source:-
What kind of format is this actually?
{
id=102,
brand=Disha,
book=[{
slr=EFTR,
description=Grammer,
data=TYR,
rate=true,
numberOfPages=345,
maxAllowed=12,
currentPage=345
},
{
slr=EFRE,
description=English,
data=TYR,
rate=true,
numberOfPages=345,
maxAllowed=12,
currentPage=345
}]
}
I want to convert this into actual JSON format like this: -
{
"id": "102",
"brand": "Disha",
"book": [{
"slr": "EFTR",
"description": "Grammer",
"data": "TYR",
"rate": true,
"numberOfPages": 345,
"maxAllowed": "12",
"currentPage": 345
},
{
"slr": "EFRE",
"description": "English",
"data": "TYR",
"rate": true,
"numberOfPages": 345,
"maxAllowed": "12",
"currentPage": 345
}]
}
Is this achievable using groovy command or code?
Couple of things:
You do not need Groovy Script test step which is currently there as step3
For step2, Add a 'Script Assertion` with given below script
Provide step name for nextStepName in the script below for which you want to add the request.
//Provide the test step name where you want to add the request
def nextStepName = 'step4'
def setRequestToStep = { stepName, requestContent ->
context.testCase.testSteps[stepName]?.httpRequest.requestContent = requestContent
}
//Check the response
assert context.response, 'Response is empty or null'
setRequestToStep(nextStepName, context.response)
EDIT: Based on the discussion with OP on the chat, OP want to update existing request of step4 for a key and its value as step2's response.
Using samples to demonstrate the change input and desired outputs.
Let us say, step2's response is:
{
"world": "test1"
}
And step4's existing request is :
{
"key" : "value",
"key2" : "value2"
}
Now, OP wants to update value of key with first response in ste4's request, and desired is :
{
"key": {
"world": "test1"
},
"key2": "value2"
}
Here is the updated script, use it in Script Assertion for step 2:
//Change the key name if required; the step2 response is updated for this key of step4
def keyName = 'key'
//Change the name of test step to expected to be updated with new request
def nextStepName = 'step4'
//Check response
assert context.response, 'Response is null or empty'
def getJson = { str ->
new groovy.json.JsonSlurper().parseText(str)
}
def getStringRequest = { json ->
new groovy.json.JsonBuilder(json).toPrettyString()
}
def setRequestToStep = { stepName, requestContent, key ->
def currentRequest = context.testCase.testSteps[stepName]?.httpRequest.requestContent
log.info "Existing request of step ${stepName} is ${currentRequest}"
def currentReqJson = getJson(currentRequest)
currentReqJson."$key" = getJson(requestContent)
context.testCase.testSteps[stepName]?.httpRequest.requestContent = getStringRequest(currentReqJson)
log.info "Updated request of step ${stepName} is ${getStringRequest(currentReqJson)}"
}
setRequestToStep(nextStepName, context.request, keyName)
We can convert the invalid JSON format to valid JSON format using this line of code:-
def validJSONString = JsonOutput.toJson(invalidJSONString).toString()

Python processing nested json

I am feeding the following data to my python script as an argument:
source=Blabla||
name=TEST Error in Log - Error: 3345||
item_value={ "Error": 3345, "Message": "Some error occurred", "Status": 1 }
The script:
#!/usr/bin/env python
import sys, json
data=sys.argv[3]
if 'NOTE: Escalation cancelled' in data:
exit(0)
data=data.split('||')
datadictionary = {}
for item in data:
key, val = item.split("=", 1)
k=key.strip()
v=(((((val.strip())).replace('\n',"<br>")).replace("`", "&lsquo")))
datadictionary[ k ] = v
datajson = json.dumps(datadictionary)
datajson=datajson.encode('utf8')
print(datajson)
It gives the following output:
{"source": "Blabla", "name": "TEST Error in Log - Error: 3345", "item_value": "{ \"Error\": 3345, \"Message\": \"Some error occurred\", \"Status\": 1 }"}
Which is not a proper JSON as item_value is a string rather than a JSON object. How can I generate a nested JSON here?
I figured out that I need to first decode the JSON object first by the following line:
datadictionary['item_value']=json.loads(datadictionary['item_value'])
So the script looks like this:
#!/usr/bin/env python
import sys, json
data=sys.argv[3]
if 'NOTE: Escalation cancelled' in data:
exit(0)
data=data.split('||')
datadictionary = {}
for item in data:
key, val = item.split("=", 1)
k=key.strip()
v=val.strip().replace('\n',"<br>").replace("`", "&lsquo")
datadictionary[ k ] = v
datadictionary['item_value']=json.loads(datadictionary['item_value'])
datajson = json.dumps(datadictionary)
datajson=datajson.encode('utf8')
print(datajson)
And the output:
{"source": "Blabla", "name": "TEST Error in Log - Error: 3345", "item_value": {"Status": 1, "Message": "Some error occurred", "Error": 3345}}

How to schedule a downtime in icinga2 by using icinga-api with groovy?

I'm searching for a way to schedule a downtime in icinga2 with a groovy script.
I already tried creating a small groovy script. Tried using the examples from icinga documentation:
curl -u root:icinga -k -s 'https://localhost:5665/v1/actions/schedule-downtime?type=Host&filter=host.vars.os==%22Linux%22' -d '{ "author" : "michi", "comment": "Maintenance.", "start_time": 1441136260, "end_time": 1441137260, "duration": 1000 }' -X POST | python -m json.tool
but adapting this to my script didn't work. Very important are the " around each attribute name, I noted.
Solution was this way:
Using wslite as webservice client. This is the minimal example.
Now I connect to my server with api enabled. The certificate is self signed, why "sslTrustAllCerts" was needed.
I select all services from my host "testserver" and set the downtime (duration in seconds).
#Grab('com.github.groovy-wslite:groovy-wslite:1.1.2')
import wslite.rest.*
import wslite.http.auth.*
def client = new RESTClient("https://myicinga2server:5665/")
client.authorization = new HTTPBasicAuthorization("root", "secret")
def timeFrom = System.currentTimeMillis() / 1000L
def timeDurationSec = 600
def timeTo = timeFrom + timeDurationSec
try
{
def response = client.post(
path: '/v1/actions/schedule-downtime?type=Service&filter=host.name==%22testserver%22',
headers: ["Accept": "application/json"],
sslTrustAllCerts: true)
{
json "author":"mstein", "comment":"Test-Downtime", "start_time": timeFrom, "end_time": timeTo, "duration": timeDurationSec, "fixed": true
}
assert 200 == response.statusCode
print response.text
}
catch (Exception exc)
{
println "Error: " + exc.getClass().toString()
println "Message: " + exc.getMessage()
println "Response: " + exc.getResponse()
System.exit(1)
}
That worked for me!