Aws Lambda makes it simple to build serverless applications. This tutorial is to build a simple Slackbot using API Gateway, DynamoDB, and Lambda using Python. Send a key to lambda. Return a value to Slack.
This is part of a larger webinar around logging a distributed system. Click here to view full webinar.
Below is the extended section on building a Slackbot.
Pre Reqs
- AWS Account
- Slack Account
- AWS CLI
Instructions
Video Tutorial Here
Create Slack Bot
- Create lambda function
- Create from blueprint
- Select “slack-echo-command-python”
- Name the function
- Create policy in new tab https://console.aws.amazon.com/iam/home?region=us-east-1#/policies > Select “JSON” > paste the following
Policy
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"lambda:CreateFunction",
"lambda:TagResource",
"lambda:GetLayerVersion",
"logs:*",
"lambda:PublishLayerVersion",
"lambda:DeleteProvisionedConcurrencyConfig",
"dynamodb:*",
"lambda:InvokeAsync",
"lambda:GetAccountSettings",
"lambda:GetFunctionConfiguration",
"lambda:CreateEventSourceMapping",
"lambda:GetLayerVersionPolicy",
"lambda:UntagResource",
"lambda:PutFunctionConcurrency",
"lambda:GetProvisionedConcurrencyConfig",
"lambda:ListTags",
"kms:*",
"lambda:DeleteLayerVersion",
"lambda:PutFunctionEventInvokeConfig",
"lambda:DeleteFunctionEventInvokeConfig",
"lambda:DeleteFunction",
"lambda:GetAlias",
"lambda:UpdateFunctionEventInvokeConfig",
"lambda:UpdateEventSourceMapping",
"lambda:GetEventSourceMapping",
"lambda:InvokeFunction",
"apigateway:*",
"lambda:GetFunction",
"lambda:UpdateFunctionConfiguration",
"lambda:UpdateAlias",
"lambda:UpdateFunctionCode",
"cloudwatch:*",
"lambda:GetFunctionConcurrency",
"lambda:GetFunctionEventInvokeConfig",
"lambda:PutProvisionedConcurrencyConfig",
"lambda:DeleteAlias",
"lambda:PublishVersion",
"lambda:DeleteFunctionConcurrency",
"lambda:DeleteEventSourceMapping",
"lambda:GetPolicy",
"lambda:CreateAlias"
],
"Resource": "*"
}
]
}
- Create role https://console.aws.amazon.com/iam/home?region=us-east-1#/roles$new?step=type
- Select Lambda under “Choose Use Case” then press “Next: Permissions”
- Search for the policy you just created
- Next: Tags > Next: Review
- Name it and “Create Role”
- Go back to your previous tab and under “Execution role” select the role you just created
- Under “API Gateway trigger” select “Create New”
- choose “REST API”
- under “Security” select “Open”
- do not check “Enable metrics and error logging” we will do that later
- Create new KMS service https://console.aws.amazon.com/kms/home?region=us-east-1#/kms/keys/create
- select Symmetric
- assign an admin i used my user account
- select the role we created above
- navigate to slack.com/apps
- Search for Slash Commands
- Create new give name
/slackbot-name
- copy the token
- paste the token
- Expand Encryption configuration
- Enable helpers for encryption in transit
- Once the function is created find api gateway in the “source” section and expand it. Paste the url in slack
- Save.
- Create DDB Table aws dynamodb create-table –cli-input-json file://table.json
Table Schema table.json
{
"AttributeDefinitions": [
{
"AttributeName": "Id",
"AttributeType": "N"
}
],
"ProvisionedThroughput": {
"WriteCapacityUnits": 5,
"ReadCapacityUnits": 5
},
"TableName": "ProductCatalog",
"KeySchema": [
{
"KeyType": "HASH",
"AttributeName": "Id"
}
]
}
25. Write sample date or your own data. aws dynamodb batch-write-item –request-items file://ProductCatalog.json
Product Catalog ProductCatalog.json
{
"ProductCatalog": [
{
"PutRequest": {
"Item": {
"Id": {
"N": "101"
},
"Title": {
"S": "Book 101 Title"
},
"ISBN": {
"S": "111-1111111111"
},
"Authors": {
"L": [
{
"S": "Author1"
}
]
},
"Price": {
"N": "2"
},
"Dimensions": {
"S": "8.5 x 11.0 x 0.5"
},
"PageCount": {
"N": "500"
},
"InPublication": {
"BOOL": true
},
"ProductCategory": {
"S": "Book"
}
}
}
},
{
"PutRequest": {
"Item": {
"Id": {
"N": "102"
},
"Title": {
"S": "Book 102 Title"
},
"ISBN": {
"S": "222-2222222222"
},
"Authors": {
"L": [
{
"S": "Author1"
},
{
"S": "Author2"
}
]
},
"Price": {
"N": "20"
},
"Dimensions": {
"S": "8.5 x 11.0 x 0.8"
},
"PageCount": {
"N": "600"
},
"InPublication": {
"BOOL": true
},
"ProductCategory": {
"S": "Book"
}
}
}
},
{
"PutRequest": {
"Item": {
"Id": {
"N": "103"
},
"Title": {
"S": "Book 103 Title"
},
"ISBN": {
"S": "333-3333333333"
},
"Authors": {
"L": [
{
"S": "Author1"
},
{
"S": "Author2"
}
]
},
"Price": {
"N": "2000"
},
"Dimensions": {
"S": "8.5 x 11.0 x 1.5"
},
"PageCount": {
"N": "600"
},
"InPublication": {
"BOOL": false
},
"ProductCategory": {
"S": "Book"
}
}
}
},
{
"PutRequest": {
"Item": {
"Id": {
"N": "201"
},
"Title": {
"S": "18-Bike-201"
},
"Description": {
"S": "201 Description"
},
"BicycleType": {
"S": "Road"
},
"Brand": {
"S": "Mountain A"
},
"Price": {
"N": "100"
},
"Color": {
"L": [
{
"S": "Red"
},
{
"S": "Black"
}
]
},
"ProductCategory": {
"S": "Bicycle"
}
}
}
},
{
"PutRequest": {
"Item": {
"Id": {
"N": "202"
},
"Title": {
"S": "21-Bike-202"
},
"Description": {
"S": "202 Description"
},
"BicycleType": {
"S": "Road"
},
"Brand": {
"S": "Brand-Company A"
},
"Price": {
"N": "200"
},
"Color": {
"L": [
{
"S": "Green"
},
{
"S": "Black"
}
]
},
"ProductCategory": {
"S": "Bicycle"
}
}
}
},
{
"PutRequest": {
"Item": {
"Id": {
"N": "203"
},
"Title": {
"S": "19-Bike-203"
},
"Description": {
"S": "203 Description"
},
"BicycleType": {
"S": "Road"
},
"Brand": {
"S": "Brand-Company B"
},
"Price": {
"N": "300"
},
"Color": {
"L": [
{
"S": "Red"
},
{
"S": "Green"
},
{
"S": "Black"
}
]
},
"ProductCategory": {
"S": "Bicycle"
}
}
}
},
{
"PutRequest": {
"Item": {
"Id": {
"N": "204"
},
"Title": {
"S": "18-Bike-204"
},
"Description": {
"S": "204 Description"
},
"BicycleType": {
"S": "Mountain"
},
"Brand": {
"S": "Brand-Company B"
},
"Price": {
"N": "400"
},
"Color": {
"L": [
{
"S": "Red"
}
]
},
"ProductCategory": {
"S": "Bicycle"
}
}
}
},
{
"PutRequest": {
"Item": {
"Id": {
"N": "205"
},
"Title": {
"S": "18-Bike-204"
},
"Description": {
"S": "205 Description"
},
"BicycleType": {
"S": "Hybrid"
},
"Brand": {
"S": "Brand-Company C"
},
"Price": {
"N": "500"
},
"Color": {
"L": [
{
"S": "Red"
},
{
"S": "Black"
}
]
},
"ProductCategory": {
"S": "Bicycle"
}
}
}
}
]
}
26. Replace lambda python code.
import boto3
import json
import logging
import os
from base64 import b64decode
from urlparse import parse_qs
#added from webinar
from boto3 import resource
from boto3.dynamodb.conditions import Key
ENCRYPTED_EXPECTED_TOKEN = os.environ['kmsEncryptedToken']
kms = boto3.client('kms')
expected_token = kms.decrypt(CiphertextBlob=b64decode(ENCRYPTED_EXPECTED_TOKEN))['Plaintext']
logger = logging.getLogger()
logger.setLevel(logging.INFO)
def ddb(key):
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('ProductCatalog')
response = table.get_item(
Key={
'Id': key,
}
)
return response['Item']
def respond(err, res=None):
return {
'statusCode': '400' if err else '200',
'body': err.message if err else json.dumps(res),
'headers': {
'Content-Type': 'application/json',
},
}
def lambda_handler(event, context):
print(json.dumps(event))
params = parse_qs(event['body'])
token = params['token'][0]
if token != expected_token:
logger.error("Request token (%s) does not match expected", token)
return respond(Exception('Invalid request token'))
user = params['user_name'][0]
command = params['command'][0]
channel = params['channel_name'][0]
if 'text' in params:
command_text = params['text'][0]
command_value = ddb(int(command_text))
else:
command_text = ''
command_value = 'val'
response = respond(None, "%s invoked %s in %s searching for the following key %s and returned the following item %s" % (user, command, channel, command_text, command_value))
logger.info(response)
return response
27. Navigate to slack and test it out.
/slackbot-name