前言
众所周知,在Serverless领域中,触发器是FaaS必不可少的一部分;一个FaaS平台,他的触发器数量、质量以及类型,很可能会决定这个FaaS平台是否能成为“主流”平台;因为触发器不仅仅是一种功能的体现,更是解决普遍性业务诉求的一个重要途径;目前来看,各个云厂商所提供的触发器基本上都会包括API网关触发器、对象存储触发器、时间触发器等,当然也有厂商提供一定的消息触发器、日志触发器、甚至是一些SQL相关的触发器、CDN触发器等,那么在我们的实际生产生活中,这些表面上看起来“很基础”的触发器,是否可以升级成为一个有趣的“高级触发器”呢?
本文将会通过Github的Webhooks能力与API网关触发器/Http触发器进行结合,抛砖引玉的讲述一下“Github触发器”和“Issue”机器人相关的例子,同时本文将会使用Serverless Devs开发者工具,以阿里云函数计算为事例进行实践。
Github触发器的实现
在使用Github的时候,细心的小伙伴会发现,无论是在某个仓库的设置下,还是某个组织的设置下,Github有一个叫Webhooks的能力:
这个能力其实就是一个非常简单的事件触发配置入口,也就是说,某些操作是可以通过在这里进行配置,然后按照我们的配置,将一些指定事件的结构体传给我们所制定的接口。
创建Http函数
为了验证我们刚才的说法,我们可以先配置一个Http函数:
$ s init python3-http
正在初始化......
初始化信息:
包名:python3-http 云厂商:Alibaba
初始化成功
此时此刻,我们可以对配置进行部分修改,例如增加日志配置等,例如在Properties的Service下,增加:
Log: Auto
即可,增加后的配置:
MyFunctionDemo:
Access: release
Component: fc
Provider: alibaba
Properties:
Region: cn-hangzhou
Service:
Name: ServerlessToolProject
Description: 欢迎使用ServerlessTool
Log: Auto
Function:
Name: serverless_demo_python3_http
Description: 这是一个Python3-HTTP的测试案例
CodeUri: ./
Handler: index.handler
MemorySize: 128
Runtime: python3
Timeout: 5
Triggers:
- Name: TriggerNameHttp
Type: HTTP
Parameters:
AuthType: ANONYMOUS
Methods:
- GET
- POST
- PUT
Domains:
- Domain: Auto
接下来,可以通过:
s deploy
进行部署,选择好密钥等信息之后,稍等片刻即可看到已经完成部署:
......
Trigger: ServerlessToolProject@serverless_demo_python3_http-TriggerNameHttp deploy successfully
......
MyFunctionDemo:
Service: ServerlessToolProject
Function: serverless_demo_python3_http
Triggers:
- Name: TriggerNameHttp
Type: HTTP
Domains:
- 34215716-1295939377467795.test.functioncompute.com
配置Webhook
然后将生成的地址,刚才部署之后生成的地址,放在Github的Webhook中:
完成之后,点击保存(Add webhook)即可,稍等片刻,我们可以看到我们添加的前面,多了一个绿色的对号:
我们此时可以点进去看一下相关的请求记录,包括request和response等相关信息:
至此,我们的一个Github 触发器。
接下来,我们就可以有针对性的做一些有趣的事情了,例如没有人提Issue可以得到一个推送等。
Github Issue的识别
为了获取到用户提的Issue内容,我们可以先对Webhook内容进行设置:
我们可以将刚才的设置"Just the push event"更改为“Let me select individual events”,并且选择Issues,保存之后,我们提交一个Issue,并且在进行request的查看:
可以看到,我们刚才的Issue已经被发送到了函数内,我们接下来,对函数代码进行升级,看看是否可以捕捉到action为opened的issue详情:
# -*- coding: utf-8 -*-
import json
import uuid
# Response
class Response:
def __init__(self, start_response, response, errorCode=None):
self.start = start_response
responseBody = {
'Error': {"Code": errorCode, "Message": response},
} if errorCode else {
'Response': response
}
# 默认增加uuid,便于后期定位
responseBody['ResponseId'] = str(uuid.uuid1())
print("Response: ", json.dumps(responseBody))
self.response = json.dumps(responseBody)
def __iter__(self):
status = '200'
response_headers = [('Content-type', 'application/json; charset=UTF-8')]
self.start(status, response_headers)
yield self.response.encode("utf-8")
def handler(environ, start_response):
try:
request_body_size = int(environ.get('CONTENT_LENGTH', 0))
except (ValueError):
request_body_size = 0
requestBody = json.loads(environ['wsgi.input'].read(request_body_size).decode("utf-8"))
responseData = "not issue opened"
if requestBody['action'] == 'opened':
print("title: ", requestBody['issue']['title'])
print("url : ", requestBody['issue']['url'])
print("body : ", requestBody['issue']['body'])
responseData = "issue opened"
return Response(start_response, {"result": responseData})
由于我们只更新了项目代码,所以我们可以直接部署代码:
s deploy function --code
当然,如果继续使用:
s deploy
进行项目部署,也是可以的,部署完成:
Waiting for function serverless_demo_python3_http to be deployed...
Only deploy function code.
Packing ...
file .s is ignored.
Package complete.
Function: ServerlessToolProject@serverless_demo_python3_http updating ...
Deploy function serverless_demo_python3_http successfully
function serverless_demo_python3_http deploy success
......
MyFunctionDemo:
Function: serverless_demo_python3_http
我们可以在命令行进行日志查看:
s logs -t
然后我们在页面进行Issue的创建:
创建完成,即可在我们的日志上看到:
至此,我们完成了一个Github Issue监控的小功能。
钉钉机器人/企业微信机器人的实现
其实,无论是钉钉机器人,还是企业微信机器人,都是一个Webhook的地址,我们按照要求传入参数即可。此时,我们可以创建一个钉钉机器人:
创建之后,可以获得Hook地址,我们将Hook地址保存,并且通过以下代码发起请求即可:
# -*- coding: utf-8 -*-
import urllib.request
url = "https://"
headers = {
"Content-Type": "application/json"
}
urllib.request.urlopen(urllib.request.Request(url, json.dumps({
"msgtype": "text",
"text": {
"content": "body"
}
}).encode("utf-8"), headers=headers))
这里要注意,针对不同类型的消息,所传输的消息类型是不同的,这一部分是可以参考开发文档来进行:
例如钉钉的Text格式:
{
"msgtype": "text",
"text": {
"content": "我就是我, 是不一样的烟火@156xxxx8827"
},
"at": {
"atMobiles": [
"156xxxx8827",
"189xxxx8325"
],
"isAtAll": false
}
}
Markdown格式:
{
"msgtype": "markdown",
"markdown": {
"title":"杭州天气",
"text": "#### 杭州天气 @156xxxx8827\n" +
"> 9度,西北风1级,空气良89,相对温度73%\n\n" +
"> ![screenshot](https://gw.alicdn.com/tfs/TB1ut3xxbsrBKNjSZFpXXcXhFXa-846-786.png)\n" +
"> ###### 10点20分发布 [天气](http://www.thinkpage.cn/) \n"
},
"at": {
"atMobiles": [
"156xxxx8827",
"189xxxx8325"
],
"isAtAll": false
}
}
在企业微信机器人中的Markdown格式:
{
"msgtype": "markdown",
"markdown": {
"content": "实时新增用户反馈<font color=\"warning\">132例</font>,请相关同事注意。\n
>类型:<font color=\"comment\">用户反馈</font> \n
>普通用户反馈:<font color=\"comment\">117例</font> \n
>VIP用户反馈:<font color=\"comment\">15例</font>"
}
}
所以,这里具体使用什么格式,可以根据个人需求而定。当然,企业微信机器人与钉钉机器人一样,都是要获得一个Webhook地址,然后发送请求即可。
Issue机器人的实现
我们将上面两部份代码整合,可以得到相对完整的代码:
import json
import uuid
import urllib.request
# Response
class Response:
def __init__(self, start_response, response, errorCode=None):
self.start = start_response
responseBody = {
'Error': {"Code": errorCode, "Message": response},
} if errorCode else {
'Response': response
}
# 默认增加uuid,便于后期定位
responseBody['ResponseId'] = str(uuid.uuid1())
print("Response: ", json.dumps(responseBody))
self.response = json.dumps(responseBody)
def __iter__(self):
status = '200'
response_headers = [('Content-type', 'application/json; charset=UTF-8')]
self.start(status, response_headers)
yield self.response.encode("utf-8")
def handler(environ, start_response):
try:
request_body_size = int(environ.get('CONTENT_LENGTH', 0))
except (ValueError):
request_body_size = 0
requestBody = json.loads(environ['wsgi.input'].read(request_body_size).decode("utf-8"))
responseData = "not issue opened"
if requestBody['action'] == 'opened':
print("title: ", requestBody['issue']['title'])
print("url : ", requestBody['issue']['url'])
print("body : ", requestBody['issue']['body'])
url = "https://"
headers = {
"Content-Type": "application/json"
}
urllib.request.urlopen(urllib.request.Request(url, json.dumps({
"msgtype": "text",
"text": {
"content": "body"
}
}).encode("utf-8"), headers=headers))
responseData = "issue opened"
return Response(start_response, {"result": responseData})
这其中:
url = "https://"
是我们的钉钉群机器人或者企业微信机器人的Webhook地址。结构:
{
"msgtype": "text",
"text": {
"content": "body"
}
}
是我们根据钉钉群机器人或者企业微信机器人所提供的文旦,针对不同类型的消息所要求的数据结构。例如我此时要用钉钉群机器人,所以我设置的结果为:
tempText = 'Issue: \nUser: %s \nMessage: %s \n[链接地址](%s)'%(jsonData['sender']['login'], jsonData['issue']['body'] ,jsonData['issue']['html_url'])
data = {
"msgtype": "markdown",
"markdown": {
"title": "Github Issue提醒",
"text": tempText
}
}
最终表现形式为,当有人在Github上提出Issue之后,我的群会进行这样的提示:
至此,我们完成一个Issue机器人。
总结
这篇文章是比较简单和有趣的,通过本文你可以简单地知道如何更加灵活的使用Http触发器/API网关触发器,也可以知道Github的Webhooks和函数计算的一个有趣的结合,也可以知道钉钉机器人和企业微信机器人的制作方法。同时,通过本文,还可以进行一个比较完整的实践:通过Github Webhooks + 钉钉群机器人/企业微信机器人 + 函数计算 + Http触发器/API网关触发器,实现一个Github Issue机器人。
当然,本文也是抛砖引玉,在实际操作过程中,可以有更多的思路融入其中,例如通过Webhook与Serverless实现一个CI/CD的小工具 .......其实Serverless可以做的事情有很多,主要在于,我们是否能够“大开脑洞”去创造,去发现更多的玩法