How can I troubleshoot my AWS Lambda function? - python

I created a lambda function in AWS and apparently it is throwing an error. Here it is:
import json
from urllib.error import HTTPError, URLError
from urllib.request import Request, urlopen
import boto3
#Create a SSM Client to access parameter store
ssm = boto3.client('ssm')
def lambda_handler(event, context):
# TODO implement
#return {
# 'statusCode': 200,
# 'body': json.dumps('Hello from Lambda!')
#}
slack_message = {
'text' = f'Hello World'
}
#retrieve webhook url from parameter store
webhook_url = ssm.get_parameter(Name='slackwebhookurl', WithDecryption=True)
#make request to the API
req = Request(webhook_url['Parameter']['Value'],
json.dumps(slack_message).encode('utf-8'))
try:
response = urlopen(req)
response.read()
print("Messge posted to Slack")
except HTTPError as e:
print(f'Request failed: {e.code} {e.reason})
except URLError as e:
print(f'Server Connection failed: {e.reason})
It's triggered by an AWS SNS notification. It's supposed to grab a webhook url for a slack channel and then send the notification to Slack.
Can anyone see what the problem is?
If it's not obvious, can someone direct me to a tutorial on how to test AWS Lambda functions?
Thanks.

AWS Lambda has an in-built Test functionality. You can click the Test button and configure an input in case the function uses values from event.
Logs will be displayed within the Lambda console.
Also, ensure that the IAM Role associated with the AWS Lambda function has the AWSLambdaBasicExecutionRole permission policy so that it can write to CloudWatch Logs. Then, you can go to the Monitoring tab of the function and click View logs in CloudWatch to see past logs.
You can add print() statements in your code, which will appear in the Logs.

Related

Acknowledge request has been received to the user

I want to send a acknowledgement to the user that request has been received and FUNCTION_NAME has been called. The FUNCTION_NAME internally calls other functions and takes about 10-15 mins to complete. So the user is acknowledged that his request is captured for processing. Any hints/leads towards handing this approaching
import os
import json
import boto3
import json as js
lambda_client = boto3.client('lambda')
def lambda_handler(event, context):
res=lambda_client.invoke(FunctionName='FUNCTION_NAME',InvocationType='RequestResponse',Payload=js.dumps(event))
res=res['Payload'].read().decode()
data= js.loads(res)['body']
status=js.loads(res)['statusCode']
return {
'isBase64Encoded':"false",
'statusCode': status,
'body': data
}
As mentioned in the boto3 documentation, you can invoke the other lambda synchronously or asynchronously.
To invoke the lambda asynchronously, change the InvocationType to Event.
See the documentation for reference.
Please do not use invoke_async, this is deprecated.

Python script http request working locally, but not when tested as a Google Cloud Function

I have a Python script that I would like to run at a set interval using Google Cloud Functions and Google Cloud Scheduler. The script works fine when tested locally, but when I test it in the Google Cloud Functions panel I'm getting a network connection error message for some reason? Do I need to do something special to get the requests library to work when the Python script is a Google Cloud Function?
Python script:
import datetime
from config import config_vars
import requests
APIKEY = config_vars['APIKEY']
NOW = datetime.datetime.now()
LAST = NOW - datetime.timedelta(seconds=config_vars['UPDATE_INTERVAL'])
def getOrders(nextPage = None):
url = "https://api.squarespace.com/1.0/commerce/orders"
if nextPage is None:
params = {
'modifiedAfter': f"{LAST.isoformat()}Z",
'modifiedBefore': f"{NOW.isoformat()}Z"
}
else:
params = { 'cursor': nextPage }
headers = { "Authorization": f"Bearer {SAPIKEY}" }
r = requests.get(url, params=params, headers=headers)
if not r.ok:
logging.error(f"Unable to get orders. Respoonse: {r.text}")
return []
res = r.json()
pagination = res['pagination']
if pagination['hasNextPage']: return res['result'] + getOrders(pagination['nextPageCursor'])
else: return res['result']
def main(data = None, context = None):
"""Triggered from a message on a Cloud Pub/Sub topic.
Args:
data (dict): Event payload.
context (google.cloud.functions.Context): Metadata for the event.
"""
orders = getOrders()
for order in orders:
# do something with each order
pass
if __name__ == '__main__': main()
Error message:
HTTPSConnectionPool(host='api.squarespace.com', port=443): Max retries exceeded with url: /1.0/commerce/orders?modifiedAfter=2020-02-09T23%3A01%3A44.372818Z&modifiedBefore=2020-02-09T23%3A01%3A45.372818Z (Caused by NewConnectionError('<urllib3.connection.VerifiedHTTPSConnection object at 0x7eedecb76850>: Failed to establish a new connection: [Errno -3] Temporary failure in name resolution'))
You need to enable billing for your project. You won't be able to make outbound requests to any URL until it is enabled.
The "Billing enabled" answer worked for me initially - However, I was mystified by a later occurrence of this same message on a Function / project where billing was definitely enabled, and in fact I could make some outbound requests, but one in particular was failing. It turned out to be a \n at the end of the URL string I had been sending to the function as a parameter. In my particular case, since I was using PHP to generate the string, a simple trim() call removed the cruft and the function continued to work as expected. Posting just in case it helps someone else, as this had me scratching my head for a bit.

Is it possible to make a POST request to Slack with AWS Lambda using only native libraries?

I am trying to make a POST request to Slack using webhooks. I can send a curl to my Slack instance locally but when trying to do so in lambda I run into trouble trying to send the payload.
Everything I've seen says I must use and zip custom libraries but for the purposes of what I'm doing I need to use native python code. Is there a way to send this POST request?
import json
import urllib.request
#import botocore.requests as requests
def lambda_handler(event, context):
message=event['message']
response = urllib.request.urlopen(message)
print(response)
This code gives me a 400 error which is how I know I'm hitting the URL I want (URL is in the message variable) but every attempt at sending a payload by adding headers and a text body seems to fail.
You may try as below:
SLACK_URL = 'https://hooks.slack.com/services/....'
req = urllib.request.Request(SLACK_URL, json.dumps(message).encode('utf-8'))
response = urllib.request.urlopen(req)
Please find attached lambda_handler code, hope this helps you.
All the messages to be posted on slack are put on a SNS topic which in turn is read by a lambda and posted to the slack channel using the slack webhook url.
import os
import json
from urllib2 import Request, urlopen, URLError, HTTPError
# Get the environment variables
SLACK_WEBHOOK_URL = os.environ['SLACK_WEBHOOK_URL']
SLACK_CHANNEL = os.environ['SLACK_CHANNEL']
SLACK_USER = os.environ['SLACK_USER']
def lambda_handler(event, context):
# Read message posted on SNS Topic
message = json.loads(event['Records'][0]['Sns']['Message'])
# New slack message is created
slack_message = {
'channel': SLACK_CHANNEL,
'username': SLACK_USER,
'text': "%s" % (message)
}
# Post message on SLACK_WEBHOOK_URL
req = Request(SLACK_WEBHOOK_URL, json.dumps(slack_message))
try:
response = urlopen(req)
response.read()
print(slack_message['channel'])
except HTTPError as e:
print(e)
except URLError as e:
print(e)

Python lambda function returns KeyError

I'm trying to create simple Lambda function using Python 3.6.
The function should get a userId (my primary key in DynamoDB) in the request query string params and returns 200 if item exist in DB, here is my lambda function
import boto3
import os
from boto3.dynamodb.conditions import Key, Attr
def lambda_handler(event, context):
userId = event["userId"]
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table(os.environ['Customers'])
items = table.query(
KeyConditionExpression=Key('userId').eq(userId)
)
return items["Items"]
When i am doing tests in Lambda interface it works and return the correct user however, when trying from Postman or using API Gateway it returns the following error
{
"errorMessage": "'userId'",
"errorType": "KeyError",
"stackTrace": [
[
"/var/task/index.py",
7,
"lambda_handler",
"userId = event["userId"]"
]
]
}
What am i missing here ?
Struggling to understand "event" , documentation states its a python
dictionary but how can i print the result of it and actually debug the lambda
when called from Postman or API Gateway?
You are using event["userId"], this means that sending the request payload for example
GET API : api/users/
Request Body payload:
{
"userId":"1234"
}
then above code works, Suppose you want to send userId as path parameter
GET API :api/user/{userId}
then you can access in lambda function
userId = (event['pathparameters']['userId'])
better add the print statement
print(event) and check the logs in cloudwatch logs
This solved it for me on post requests
import json
def lambda_handler(event, context):
data = json.loads(event["body"])
email = data['email']
in case you are using the serverless framework you can also add the following code under your http event. but i dont think it is that necessary.
request:
parameters:
application/json: '{"email":"$input.params(''email'')"}'
Make sure you hadn't selected "Lambda Proxy" while creating the HTTP method. Proxy will not convert/modify the request and hence "event" will be null
In my case my Python Lambda required a key called exclude. To resolve the issue of getting this response when calling via API Gateway, I needed to update the integration request with a mapping template:

django/python the works of views and bringing in an API

I'm just beginning to learn about python/django. I know PHP, but I wanted to get to know about this framework. I'm trying to work with yelp's API. I'm trying to figure out what to do when someone brings in a new file into the project.
In their business.py they have this:
import json
import oauth2
import optparse
import urllib
import urllib2
parser = optparse.OptionParser()
parser.add_option('-c', '--consumer_key', dest='consumer_key', help='OAuth consumer key (REQUIRED)')
parser.add_option('-s', '--consumer_secret', dest='consumer_secret', help='OAuth consumer secret (REQUIRED)')
parser.add_option('-t', '--token', dest='token', help='OAuth token (REQUIRED)')
parser.add_option('-e', '--token_secret', dest='token_secret', help='OAuth token secret (REQUIRED)')
parser.add_option('-a', '--host', dest='host', help='Host', default='api.yelp.com')
parser.add_option('-i', '--id', dest='id', help='Business')
parser.add_option('-u', '--cc', dest='cc', help='Country code')
parser.add_option('-n', '--lang', dest='lang', help='Language code')
options, args = parser.parse_args()
# Required options
if not options.consumer_key:
parser.error('--consumer_key required')
if not options.consumer_secret:
parser.error('--consumer_secret required')
if not options.token:
parser.error('--token required')
if not options.token_secret:
parser.error('--token_secret required')
if not options.id:
parser.error('--id required')
url_params = {}
if options.cc:
url_params['cc'] = options.cc
if options.lang:
url_params['lang'] = options.lang
path = '/v2/business/%s' % (options.id,)
def request(host, path, url_params, consumer_key, consumer_secret, token, token_secret):
"""Returns response for API request."""
# Unsigned URL
encoded_params = ''
if url_params:
encoded_params = urllib.urlencode(url_params)
url = 'http://%s%s?%s' % (host, path, encoded_params)
print 'URL: %s' % (url,)
# Sign the URL
consumer = oauth2.Consumer(consumer_key, consumer_secret)
oauth_request = oauth2.Request('GET', url, {})
oauth_request.update({'oauth_nonce': oauth2.generate_nonce(),
'oauth_timestamp': oauth2.generate_timestamp(),
'oauth_token': token,
'oauth_consumer_key': consumer_key})
token = oauth2.Token(token, token_secret)
oauth_request.sign_request(oauth2.SignatureMethod_HMAC_SHA1(), consumer, token)
signed_url = oauth_request.to_url()
print 'Signed URL: %s\n' % (signed_url,)
# Connect
try:
conn = urllib2.urlopen(signed_url, None)
try:
response = json.loads(conn.read())
finally:
conn.close()
except urllib2.HTTPError, error:
response = json.loads(error.read())
return response
response = request(options.host, path, url_params, options.consumer_key, options.consumer_secret, options.token, options.token_secret)
print json.dumps(response, sort_keys=True, indent=2)
Its very lengthy, I appologize for that. But my concern is what do I do with this? They've set up a def request() in here, and I'm assuming that I have to import this into my views?
I've been following the django documentation of creating a new app. In the documentation they've set up a bunch of def inside the views.py file. I'm just confused as to how am I supposed to make this work with my project? If I wanted to search for a business in the URL, how would it send the data out?
Thanks for your help.
This is a command line script that makes http requests to the yelp api. You probably don't want to make such an external request within the context of a main request handler. Well, you could call a request handler that makes this call to yelp. Let's see ...
You could import the request function and instead of invoking it with command line options, call it yourself.
from yelp.business import request as yelp_req
def my_request_handler(request):
json_from_yelp = yelp_req(...
# do stuff and return a response
Making this kind of external call inside a request handler is pretty meh though (that is, making an http request to an external service within a request handler). If the call is in ajax, it may be ok for the ux.
This business.py is just an example showing you how to create a signed request with oauth2. You may be able to just import the request function and use it. OTOH, you may prefer to write your own (perhaps using the requests library). You probably want to use celery or some other async means to make the calls outside of your request handlers and/or cache the responses to avoid costly external http io with every request.

Categories