① Perform a specific action in Slack. (* This time, the slash command) (2) Start Lambda via API Gateway. ③ Execute the function placed in Lambda and return the information.
↑ It looks like this as an operation image. This time, I will move SlackBot, which returns the current temperature in a certain area.
--People who want to make a simple SlackBot --People who want to touch AWS Lambda
AWS Lambda is a computing service that allows you to execute code without having to provision or manage servers. AWS Lambda runs your code only when you need it, and automatically scales from a few requests per day to thousands of requests per second. You only pay for the compute time you use-no charges unless the code is running. AWS Lambda allows you to virtually execute code in any application or back-end service with no management required. AWS Lambda runs code on a highly available computing infrastructure and manages all of your computing resources, including server and operating systems, system maintenance, capacity provisioning and autoscaling, code monitoring and logging. To execute. All you have to do is specify the code in one of the languages AWS Lambda supports. (Quote: Amazon official document)
It's hard to understand if it's just this, but in short, it means that you don't need a server to execute the program.
Normally, if you want to run some program, it takes a lot of trouble to purchase a server and install various middleware, but in Lambda, AWS manages all of that, so developers It seems that you only have to focus on creating the source code.
--No need to manage servers and various middleware --For the reasons mentioned above.
In the case of a SlackBot like this one, I think it's just right to use Lambda because it doesn't have to be always up and running only when needed.
If the implementation is as light as SlackBot, the deployment method using Heroku etc. is standard, but I feel like using AWS nowadays.
Language: Ruby2.5 Framework: Sinatra Infrastructure: AWS Lambda
Completed form: slack-bot-on-aws-lambda
First, create the essential SlackBot.
$ mkdir slack-bot-on-aws-lambda
$ cd slack-bot-on-aws-lambda
# 2.Any 5 series is OK
$ rbenv local 2.5.1
$ bundle init
Create a Gemfile with the ↑ command and edit it as follows.
./Gemfile
# frozen_string_literal: true
source "https://rubygems.org"
git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
gem 'sinatra'
Then install Gem.
$ bundle install --path vendor/bundle
To check the operation, I will implement a page that returns "Hello World!" For the time being.
$ touch main.rb
ruby:./main.rb
require 'sinatra'
get '/' do
'Hello World!'
end
After that, start Sinatra.
$ bundle exec ruby main.rb
[2020-09-21 20:47:35] INFO WEBrick 1.4.2
[2020-09-21 20:47:35] INFO ruby 2.5.1 (2018-03-29) [x86_64-darwin19]
== Sinatra (v2.1.0) has taken the stage on 4567 for development with backup from WEBrick
[2020-09-21 20:47:35] INFO WEBrick::HTTPServer#start: pid=50418 port=4567
By default, Sinatra works on port number "4567", so access "[localhost: 4567](localhost: 4567)".
Success if "Hello World!" Is displayed.
We will implement a program that returns weather information, which is the main function of the SlackBot created this time.
Register as a member on the above site and get an API key.
Although the service is written in English, detailed explanation is omitted because it can be operated intuitively to some extent. If you really don't understand, you can get as many articles as you want by google.
Since there are some gems required for future processing, install them at once at this timing.
./Gemfile
gem 'faraday'
gem 'rack'
gem 'rack-contrib'
gem 'rubysl-base64'
gem 'slack-ruby-bot'
Don't forget to "bundle install".
$ bundle install --path vendor/bundle
$ mkdir src
$ touch src/weather.rb
Create src / weather.rb and write as follows.
ruby:./src/weather.rb
require 'json'
class Weather
def current_temp(locate)
end_point_url = 'http://api.openweathermap.org/data/2.5/weather'
api_key = #The OpenWeather API key you got earlier
res = Faraday.get(end_point_url + "?q=#{locate},jp&APPID=#{api_key}")
res_body = JSON.parse(res.body)
temp = res_body['main']['temp']
celsius = temp - 273.15
celsius_round = celsius.round
return "The current temperature in Nerima#{celsius_round.to_s}It is ℃."
end
end
ruby:./main.rb
require 'slack-ruby-client'
require 'sinatra'
require './src/weather'
Slack.configure do |conf|
conf.token = #SlackBot token
end
get '/' do
'This is SlackBot on AWS Lambda'
end
post '/webhook' do
client = Slack::Web::Client.new
channel_id = params['channel_id']
command = params['command']
case command
when '/nerima'
#Slash command "/When "nerima" is executed, the following processing is executed.
weather = Weather.new
client.chat_postMessage channel: channel_id, text: weather.current_temp('Nerima'), as_user: true
# 'Nerima'You can change the part of. If you change to "Shinjuku", the temperature in Shinjuku should be returned.
end
return
end
See the following article for how to get a SlackBot token.
See also: Creating a bot for your workspace (https://slack.com/intl/ja-jp/help/articles/115005265703-%E3%83%AF%E3%83%BC%E3%82% AF% E3% 82% B9% E3% 83% 9A% E3% 83% BC% E3% 82% B9% E3% 81% A7% E5% 88% A9% E7% 94% A8% E3% 81% 99% E3% 82% 8B% E3% 83% 9C% E3% 83% 83% E3% 83% 88% E3% 81% AE% E4% BD% 9C% E6% 88% 90) See also: API Token Generation and Regeneration (https://slack.com/intl/ja-jp/help/articles/215770388-API-%E3%83%88%E3%83%BC%E3%82 % AF% E3% 83% B3% E3% 81% AE% E7% 94% 9F% E6% 88% 90% E3% 81% A8% E5% 86% 8D% E7% 94% 9F% E6% 88% 90 )
There are a few things you need to do to call SlackBot with the slash command.
https://api.slack.com/apps/ Access the URL above and select the relevant bot.
There is an item called "Slash Commands" in the left side menu, so select it and click "Create New Command". Enter each item.
--Command: Any slash command. --This time, "/ nerima" is used because it is assumed that the current temperature in Nerima-ku, Tokyo will be returned, but in Shinjuku-ku, for example, "/ shinjuku" is OK. ) --Request URL: The URL you want to request when you execute the slash command. --Since it doesn't work on "localhost", this time I'm using ngrok to assign my own domain. --Reference: How to use ngrok --After starting Sinatra with "bundle exec ruby main.rb", tap "ngrok http 4567" in another terminal and use the displayed URL. --I want to make a request with the post method, so enter "https: //********. Ngrok.io/webhook". --Short Description: A brief description of the slash command.
Click "Save" at the bottom right when the input is completed.
After creating the slash command, try typing "/ nerima" on the channel where you added the SlackBot. Hopefully you will get a response from SlackBot like the image. (It is also possible to change the image and name in the settings.) If something goes wrong, the log should be output to the terminal, so debug as appropriate.
After confirming the operation normally, it is finally time to put it into production on AWS Lambda.
This time, we will deploy using a tool called "AWS CLI", so if you haven't installed it yet, install it.
$ brew install awscli
Create an IAM user to perform the deployment work. First, go to "IAM"-> "Policy"-> "Create Policy" and paste the following statement from the JSON tab.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"apigateway:*",
"cloudformation:*",
"dynamodb:*",
"events:*",
"iam:*",
"lambda:*",
"logs:*",
"route53:*",
"s3:*"
],
"Resource": [
"*"
]
}
]
}
See also: Minimal Deploy IAM Policy (https://rubyonjets.com/docs/extras/minimal-deploy-iam/)
Enter the policy name and description as appropriate, and click "Create Policy".
Next, go to "IAM"-> "User"-> "Create User", give it an appropriate name, check "Access by Program", and proceed to the next.
Select the "Minimal Deploy IAM Policy" created earlier from "Attach existing policy directly" and proceed to the next.
(Tags are optional) A confirmation screen will be displayed at the end, so if there are no problems, click "Create User".
Then, two "access key ID" and "secret access key" will be issued, so keep it in a safe place as you download the csv file or make a note of it.
$ aws configure
AWS Access Key ID #The access key ID created earlier
AWS Secret Access Key #The secret access key you created earlier
Default region name # ap-northeast-1
Default output format # json
When you type "aws configure" in the terminal, you will be asked various things interactively, so enter the necessary information for each.
After setting the AWS CLI, create various files required for deployment.
ruby:./config.ru
require 'rack'
require 'rack/contrib'
require_relative './main'
set :root, File.dirname(__FILE__)
run Sinatra::Application
ruby:./lambda.rb
require 'json'
require 'rack'
require 'base64'
$app ||= Rack::Builder.parse_file("#{__dir__}/config.ru").first
ENV['RACK_ENV'] ||= 'production'
def handler(event:, context:)
body = if event['isBase64Encoded']
Base64.decode64 event['body']
else
event['body']
end || ''
headers = event.fetch 'headers', {}
env = {
'REQUEST_METHOD' => event.fetch('httpMethod'),
'SCRIPT_NAME' => '',
'PATH_INFO' => event.fetch('path', ''),
'QUERY_STRING' => Rack::Utils.build_query(event['queryStringParameters'] || {}),
'SERVER_NAME' => headers.fetch('Host', 'localhost'),
'SERVER_PORT' => headers.fetch('X-Forwarded-Port', 443).to_s,
'rack.version' => Rack::VERSION,
'rack.url_scheme' => headers.fetch('CloudFront-Forwarded-Proto') { headers.fetch('X-Forwarded-Proto', 'https') },
'rack.input' => StringIO.new(body),
'rack.errors' => $stderr,
}
headers.each_pair do |key, value|
name = key.upcase.gsub '-', '_'
header = case name
when 'CONTENT_TYPE', 'CONTENT_LENGTH'
name
else
"HTTP_#{name}"
end
env[header] = value.to_s
end
begin
status, headers, body = $app.call env
body_content = ""
body.each do |item|
body_content += item.to_s
end
response = {
'statusCode' => status,
'headers' => headers,
'body' => body_content
}
if event['requestContext'].has_key?('elb')
response['isBase64Encoded'] = false
end
rescue Exception => exception
response = {
'statusCode' => 500,
'body' => exception.message
}
end
response
end
template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: 'AWS::Serverless-2016-10-31'
Resources:
SinatraFunction:
Type: 'AWS::Serverless::Function'
Properties:
FunctionName: SlackBot
Handler: lambda.handler
Runtime: ruby2.5
CodeUri: './'
MemorySize: 512
Timeout: 30
Events:
SinatraApi:
Type: Api
Properties:
Path: /
Method: ANY
RestApiId: !Ref SinatraAPI
SinatraAPI:
Type: AWS::Serverless::Api
Properties:
Name: SlackBotAPI
StageName: Prod
DefinitionBody:
swagger: '2.0'
basePath: '/Prod'
info:
title: !Ref AWS::StackName
paths:
/{proxy+}:
x-amazon-apigateway-any-method:
responses: {}
x-amazon-apigateway-integration:
uri:
!Sub 'arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${SinatraFunction.Arn}/invocations'
passthroughBehavior: 'when_no_match'
httpMethod: POST
type: 'aws_proxy'
/:
get:
responses: {}
x-amazon-apigateway-integration:
uri:
!Sub 'arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${SinatraFunction.Arn}/invocations'
passthroughBehavior: 'when_no_match'
httpMethod: POST
type: 'aws_proxy'
ConfigLambdaPermission:
Type: 'AWS::Lambda::Permission'
DependsOn:
- SinatraFunction
Properties:
Action: lambda:InvokeFunction
FunctionName: !Ref SinatraFunction
Principal: apigateway.amazonaws.com
Outputs:
SinatraAppUrl:
Description: App endpoint URL
Value: !Sub "https://${SinatraAPI}.execute-api.${AWS::Region}.amazonaws.com/Prod/"
I will omit here what each represents. (Because this area uses the official AWS documentation almost as it is) See also: aws-samples / serverless-sinatra-sample
Since it is necessary to prepare the AWS S3 bucket in advance, create an S3 bucket appropriately.
See: How to make a bucket for AWS S3
Execute the following command.
$ aws cloudformation package \
--template-file template.yaml \
--output-template-file serverless-output.yaml \
--s3-bucket #The S3 bucket name you created earlier
Uploading to a3a55f6abf5f21a2e1161442e53b27a8 12970487 / 12970487.0 (100.00%)
Successfully packaged artifacts and wrote output template to file serverless-output.yaml.
Execute the following command to deploy the packaged template
aws cloudformation deploy --template-file /Users/username/Directory name/serverless-output.yaml --stack-name <YOUR STACK NAME>
Then, a file called "serverless-output.yaml" should be automatically generated in the directory, so execute the following command based on this.
$ aws cloudformation deploy --template-file serverless-output.yaml \
--stack-name slack-bot \
--capabilities CAPABILITY_IAM
Waiting for changeset to be created..
Waiting for stack create/update to complete
Successfully created/updated stack - slack-bot
If "Successfully" is displayed, the deployment is successful.
If you go to "Lambda"-> "Function", the contents deployed earlier will be displayed, so access the API endpoint described in "API Gateway".
ruby:./main.rb
get '/' do
'This is SlackBot on AWS Lambda'
end
If "This is SlackBot on AWS Lambda" is returned as expected in the get'/' request in main.rb, it is OK to judge that it is working normally.
Go to the SlackBot settings page again and change the Request URL from "Slash Commands" to the endpoint you just created. (Https: //********.execute-api.ap-northeast-1.amazonaws.com/Prod/webhook ")
Finally, I typed "/ nerima" on the Slack channel again, and I was happy if the response came back properly.
If it doesn't work, the log should be output to CloudWatch, so debug it appropriately.
Thank you for your hard work!
This time, I touched AWS Lambda with the theme of running a simple SlackBot with Lambda. I couldn't really realize all the greatness of Lamda because I couldn't implement a lot of functions, but I feel that it is a very convenient service if used well.
I haven't done any difficult operations, so basically it should work if you follow the procedure, but if there is something stuck, I'd appreciate it if you could point it out in the comments.
Recommended Posts