0%

gitlab提交MR通知飞书的golang代码实现

公司团队是使用gitlab来管理源代码的,一直以来当提交了一个MR后,需要手动在内部IM群里贴出PR链接和摘要,然后@目标同事来帮忙review代码,其实大部分流程是可以自动化的。

1. 设置

1.1 gitlab

gitlab admin 用户可以在所有项目设置, webhooks, 填写地址。

1

1.2 飞书

飞书群组创建普通机器人即可,复制出来webhook url地址

1

2. golang实现

hook.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
package main

import (
"bytes"
"encoding/json"
"fmt"
"log"
"net/http"
"strings"
"time"
)

const (
// https://open.feishu.cn/tool/cardbuilder?from=custom_bot_doc
MRTemplate = `{"config":{"wide_screen_mode":true},"header":{"template":"blue","title":{"content":"{{__project__}} 👉 {{__branch__}}","tag":"plain_text"}},"i18n_elements":{"zh_cn":[{"fields":[{"is_short":false,"text":{"content":"**时间:**{{__time__}}\n\n**地址:**[{{__url__}}]({{__url__}})\n\n**提交人:**{{__name__}}\n\n**提交消息:**{{__title__}}\n{{__desc__}}","tag":"lark_md"}}],"tag":"div"},{"tag":"hr"}]}}`
)

type MrContext struct {
Project string `json:"project"`
SourceBranch string `json:"source_branch"`
Url string `json:"url"`
Title string `json:"title"`
Desc string `json:"desc"`
Time string `json:"time"`
Name string `json:"name"`
}

func ExecHook(body string, hookUrl string) {
log.Println("body:", body)

var v map[string]interface{}
if err := json.Unmarshal([]byte(body), &v); err != nil {
log.Println("json Unmarshal err:", err)
return
}

et, _ := v["event_type"].(string)
if et != "merge_request" {
log.Println("event_type not merge_request:", et, v["event_type"])
return
}

mc := MrContext{}

if project, ok := v["project"].(map[string]interface{}); ok {
if name, ok := project["name"].(string); ok {
mc.Project = name
}
}

if object_attributes, ok := v["object_attributes"].(map[string]interface{}); ok {
state, _ := object_attributes["state"].(string)
action, _ := object_attributes["action"].(string)
if state == "opened" && action == "open" {
// 新开的分支继续往下走
} else {
log.Println("fail: state", state, "action", action)
return
}

if source_branch, ok := object_attributes["source_branch"].(string); ok {
mc.SourceBranch = source_branch
}
if title, ok := object_attributes["title"].(string); ok {
mc.Title = title
}
if description, ok := object_attributes["description"].(string); ok {
mc.Desc = description
}

if update, ok := object_attributes["updated_at"].(string); ok {
t, _ := time.Parse("2006-01-02 15:04:05 MST", update)
l, _ := time.LoadLocation("Asia/Shanghai")
mc.Time = t.In(l).Format("2006-01-02 15:04:05")
}
if url, ok := object_attributes["url"].(string); ok {
mc.Url = url
}
}

if user, ok := v["user"].(map[string]interface{}); ok {
if name, ok := user["name"].(string); ok {
mc.Name = name
}
}

_, err := http.Post(hookUrl, "application/json", bytes.NewBufferString(getMrContext(mc)))
log.Println("hookUrl", hookUrl, getMrContext(mc), err)
if err != nil {
log.Println("http err:", err)
return
}
}

func getMrContext(mc MrContext) string {
str := MRTemplate
str = strings.ReplaceAll(str, "{{__project__}}", mc.Project)
str = strings.ReplaceAll(str, "{{__branch__}}", mc.SourceBranch)
str = strings.ReplaceAll(str, "{{__url__}}", mc.Url)
str = strings.ReplaceAll(str, "{{__name__}}", mc.Name)
str = strings.ReplaceAll(str, "{{__time__}}", mc.Time)
str = strings.ReplaceAll(str, "{{__title__}}", strings.ReplaceAll(mc.Title, "\n", ""))
str = strings.ReplaceAll(str, "{{__desc__}}", strings.ReplaceAll(mc.Desc, "\n", ""))
return fmt.Sprintf("{\"msg_type\":\"interactive\",\"card\":%v}", str)
}

main.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package main

import (
"io/ioutil"

"github.com/gin-gonic/gin"
)

func main() {
r := gin.Default()
r.GET("/ping", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "pong",
})
})
r.POST("gitlab-hook", GitlabHook)
r.Run(":8001")
}

func GitlabHook(c *gin.Context) {
if c.Request.Header.Get("X-Gitlab-Event") != "System Hook" {
return
}

url := "你的 web hook api 地址"
data, _ := ioutil.ReadAll(c.Request.Body)
ExecHook(string(data), url)
}

3. 配置模板

可去飞书模板界面,拖拽想要的样式,然后保存复制出来json即可。

1
https://open.feishu.cn/tool/cardbuilder?from=custom_bot_doc
给作者打赏,鼓励TA抓紧创作!