Reviewed-on: zhaoyingbo/ci_monitor#2
This commit is contained in:
commit
05b7a4a5c5
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@ -3,6 +3,7 @@
|
||||
"bunx",
|
||||
"CEINTL",
|
||||
"Chakroun",
|
||||
"cloudml",
|
||||
"commitlint",
|
||||
"dbaeumer",
|
||||
"devcontainers",
|
||||
@ -11,6 +12,8 @@
|
||||
"Gruntfuggly",
|
||||
"tseslint",
|
||||
"wlpbbgiky",
|
||||
"wujiali",
|
||||
"Wyob",
|
||||
"Yoav"
|
||||
]
|
||||
}
|
||||
|
121
controllers/managePipelineEvent/index.ts
Normal file
121
controllers/managePipelineEvent/index.ts
Normal file
@ -0,0 +1,121 @@
|
||||
import service from "../../service"
|
||||
import netTool from "../../service/netTool"
|
||||
import { Gitlab } from "../../types/gitlab"
|
||||
import { Notify } from "../../types/notify"
|
||||
import { sec2minStr } from "../../utils/timeTools"
|
||||
|
||||
/**
|
||||
* 判断是否是合并请求
|
||||
* @param pipeline
|
||||
* @returns
|
||||
*/
|
||||
const checkIsMergeCommit = (pipeline: Gitlab.PipelineEvent) => {
|
||||
const regex = /^Merge branch '.*' into '.*'$/
|
||||
return regex.test(pipeline.commit.title)
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否是成功的CI
|
||||
* @param pipeline
|
||||
* @returns
|
||||
*/
|
||||
const checkIsSuccess = (pipeline: Gitlab.PipelineEvent) => {
|
||||
return pipeline.object_attributes.status === "success"
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取合并请求
|
||||
* @param pipeline
|
||||
* @returns
|
||||
*/
|
||||
const getMergeRequest = async (pipeline: Gitlab.PipelineEvent) => {
|
||||
if (!checkIsMergeCommit(pipeline)) return null
|
||||
const res = await service.gitlab.commit.getMr(
|
||||
pipeline.project.id,
|
||||
pipeline.object_attributes.sha
|
||||
)
|
||||
if (res.length === 0) return null
|
||||
return res.find((mr) => mr.merge_commit_sha === pipeline.commit.id) || null
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户信息
|
||||
* @param pipeline
|
||||
* @param mergeRequest
|
||||
* @returns
|
||||
*/
|
||||
const getUserInfo = (
|
||||
pipeline: Gitlab.PipelineEvent,
|
||||
mergeRequest: Gitlab.MergeRequest | null
|
||||
) => {
|
||||
let participant = pipeline.user.name
|
||||
const receiver = [pipeline.user.username]
|
||||
// 有MR且用户不同
|
||||
if (mergeRequest && mergeRequest.author.username !== pipeline.user.username) {
|
||||
participant += `、${mergeRequest.author.name}`
|
||||
receiver.push(mergeRequest.author.username)
|
||||
}
|
||||
return { participant, receiver }
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取机器人消息
|
||||
* @param variable 模板变量
|
||||
* @returns
|
||||
*/
|
||||
const getRobotMsg = async (variable: Notify.CardVariable) =>
|
||||
JSON.stringify({
|
||||
type: "template",
|
||||
data: {
|
||||
config: {
|
||||
update_multi: true,
|
||||
},
|
||||
template_id: "ctp_AA36QafWyob2",
|
||||
template_variable: variable,
|
||||
},
|
||||
})
|
||||
|
||||
/**
|
||||
* 发送通知消息
|
||||
* @param pipeline
|
||||
* @param apiKey
|
||||
* @returns
|
||||
*/
|
||||
const sendNotifyMsg = async (
|
||||
pipeline: Gitlab.PipelineEvent,
|
||||
apiKey: string
|
||||
) => {
|
||||
// 只处理成功的CICD
|
||||
if (!checkIsSuccess(pipeline)) return netTool.ok()
|
||||
// 获取对应的合并请求
|
||||
const mergeRequest = await getMergeRequest(pipeline)
|
||||
// 获取用户信息
|
||||
const { participant, receiver } = getUserInfo(pipeline, mergeRequest)
|
||||
// 获取模板变量
|
||||
const variable: Notify.CardVariable = {
|
||||
project: pipeline.project.path_with_namespace,
|
||||
project_link: pipeline.project.web_url,
|
||||
pipeline: pipeline.object_attributes.id.toString(),
|
||||
pipeline_link: `${pipeline.project.web_url}/-/pipelines`,
|
||||
ref: pipeline.object_attributes.ref,
|
||||
ref_link: `${pipeline.project.web_url}/-/commits/${pipeline.object_attributes.ref}`,
|
||||
commit_user: pipeline.user.name,
|
||||
duration: sec2minStr(pipeline.object_attributes.duration),
|
||||
participant,
|
||||
commit_message: pipeline.commit.title,
|
||||
mr: mergeRequest ? mergeRequest.references.full : "无关联的MR",
|
||||
mr_link: mergeRequest ? mergeRequest.web_url : "",
|
||||
}
|
||||
// 获取机器人消息
|
||||
const robotMsg = await getRobotMsg(variable)
|
||||
// 发送消息
|
||||
await service.message.byUserIdList(receiver, robotMsg, apiKey)
|
||||
// 返回成功
|
||||
return netTool.ok()
|
||||
}
|
||||
|
||||
const managePipelineEvent = {
|
||||
sendNotifyMsg,
|
||||
}
|
||||
|
||||
export default managePipelineEvent
|
3
index.ts
3
index.ts
@ -1,4 +1,5 @@
|
||||
import { manageCIMonitorReq } from "./routes/ci"
|
||||
import { manageGitlabEventReq } from "./routes/event"
|
||||
import initSchedule from "./schedule"
|
||||
import netTool from "./service/netTool"
|
||||
|
||||
@ -13,6 +14,8 @@ const server = Bun.serve({
|
||||
if (url.pathname === "/") return netTool.ok("hello, glade to see you!")
|
||||
// CI 监控
|
||||
if (url.pathname === "/ci") return manageCIMonitorReq(req)
|
||||
// Gitlab 事件
|
||||
if (url.pathname === "/event") return manageGitlabEventReq(req)
|
||||
// 其他
|
||||
return netTool.ok("hello, glade to see you!")
|
||||
} catch (error: any) {
|
||||
|
@ -8,15 +8,8 @@ import netTool from "../../service/netTool"
|
||||
*/
|
||||
export const manageCIMonitorReq = (req: Request) => {
|
||||
const url = new URL(req.url)
|
||||
if (url.pathname === "/ci") {
|
||||
const chat_id = url.searchParams.get("chat_id")
|
||||
if (!chat_id) return netTool.badRequest("chat_id is required!")
|
||||
manageRobot.sendCIReportByChatId(chat_id)
|
||||
return Response.json({
|
||||
code: 0,
|
||||
msg: "success",
|
||||
data: "reporting...",
|
||||
})
|
||||
}
|
||||
return netTool.ok()
|
||||
const chat_id = url.searchParams.get("chat_id")
|
||||
if (!chat_id) return netTool.badRequest("chat_id is required!")
|
||||
manageRobot.sendCIReportByChatId(chat_id)
|
||||
return netTool.ok("reporting...")
|
||||
}
|
||||
|
20
routes/event/index.ts
Normal file
20
routes/event/index.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import managePipelineEvent from "../../controllers/managePipelineEvent"
|
||||
import netTool from "../../service/netTool"
|
||||
import { Gitlab } from "../../types/gitlab"
|
||||
|
||||
/**
|
||||
* 处理管理Gitlab事件的请求。
|
||||
* @param req - 请求对象。
|
||||
* @returns 响应对象。
|
||||
*/
|
||||
export const manageGitlabEventReq = async (req: Request) => {
|
||||
const apiKey = req.headers.get("x-gitlab-token")
|
||||
if (!apiKey) return netTool.badRequest("x-gitlab-token is required!")
|
||||
const eventType = req.headers.get("x-gitlab-event")
|
||||
// 只处理流水线钩子
|
||||
if (eventType === "Pipeline Hook") {
|
||||
const body = (await req.json()) as Gitlab.PipelineEvent
|
||||
return managePipelineEvent.sendNotifyMsg(body, apiKey)
|
||||
}
|
||||
return netTool.ok()
|
||||
}
|
17
script/notify/mr.json
Normal file
17
script/notify/mr.json
Normal file
@ -0,0 +1,17 @@
|
||||
[
|
||||
{
|
||||
"title": "feat: 修改错别字",
|
||||
"target_branch": "preview",
|
||||
"source_branch": "feat/pai",
|
||||
"author": {
|
||||
"username": "wujiali5",
|
||||
"name": "伍嘉丽"
|
||||
},
|
||||
"sha": "f5485b7eebf700476d6cfb27e50a85309dd144d1",
|
||||
"merge_commit_sha": "b18338018b421918aa4f5dc4da3e0b50d728fd78",
|
||||
"references": {
|
||||
"full": "cloudml-visuals/fe/cloud-ml-fe!374"
|
||||
},
|
||||
"web_url": "https://git.n.xiaomi.com/cloudml-visuals/fe/cloud-ml-fe/-/merge_requests/374"
|
||||
}
|
||||
]
|
25
script/notify/pipline.json
Normal file
25
script/notify/pipline.json
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"object_attributes": {
|
||||
"id": 8778929,
|
||||
"ref": "ci",
|
||||
"sha": "fc0fdf57c3b662b296b89dc2a289798d130d1be1",
|
||||
"status": "success",
|
||||
"created_at": "2024-07-23 08:57:14 +0800",
|
||||
"finished_at": "2024-07-23 08:57:24 +0800",
|
||||
"duration": 5
|
||||
},
|
||||
"user": {
|
||||
"name": "赵英博",
|
||||
"username": "zhaoyingbo"
|
||||
},
|
||||
"project": {
|
||||
"id": 145623,
|
||||
"web_url": "https://git.n.xiaomi.com/cloudml-visuals/fe/test",
|
||||
"path_with_namespace": "cloudml-visuals/fe/test"
|
||||
},
|
||||
"commit": {
|
||||
"id": "fc0fdf57c3b662b296b89dc2a289798d130d1be1",
|
||||
"title": "chore: Add .gitlab-ci.yml for printing \"Hello, world!\"",
|
||||
"url": "https://git.n.xiaomi.com/cloudml-visuals/fe/test/-/commit/fc0fdf57c3b662b296b89dc2a289798d130d1be1"
|
||||
}
|
||||
}
|
@ -1,81 +0,0 @@
|
||||
{
|
||||
"object_kind": "pipeline",
|
||||
"object_attributes": {
|
||||
"id": 8779191,
|
||||
"ref": "main",
|
||||
"tag": false,
|
||||
"sha": "66ac20f0f6a839f29753b17dcdf12deb91148fed",
|
||||
"before_sha": "39a21d2cb3a0d39d2fb0df81a244ebeae8989ce4",
|
||||
"source": "push",
|
||||
"status": "pending",
|
||||
"detailed_status": "pending",
|
||||
"stages": ["print"],
|
||||
"created_at": "2024-07-23 09:28:44 +0800",
|
||||
"finished_at": null,
|
||||
"duration": null,
|
||||
"queued_duration": null,
|
||||
"variables": []
|
||||
},
|
||||
"merge_request": null,
|
||||
"user": {
|
||||
"id": 30441,
|
||||
"name": "伍嘉丽",
|
||||
"username": "wujiali5",
|
||||
"avatar_url": "https://git.n.xiaomi.com/uploads/-/system/user/avatar/30441/avatar.png",
|
||||
"email": "[REDACTED]"
|
||||
},
|
||||
"project": {
|
||||
"id": 145623,
|
||||
"name": "test",
|
||||
"description": "",
|
||||
"web_url": "https://git.n.xiaomi.com/cloudml-visuals/fe/test",
|
||||
"avatar_url": null,
|
||||
"git_ssh_url": "git@git.n.xiaomi.com:cloudml-visuals/fe/test.git",
|
||||
"git_http_url": "https://git.n.xiaomi.com/cloudml-visuals/fe/test.git",
|
||||
"namespace": "fe",
|
||||
"visibility_level": 0,
|
||||
"path_with_namespace": "cloudml-visuals/fe/test",
|
||||
"default_branch": "main",
|
||||
"ci_config_path": ""
|
||||
},
|
||||
"commit": {
|
||||
"id": "66ac20f0f6a839f29753b17dcdf12deb91148fed",
|
||||
"message": "Merge branch 'ci' into 'main'\n\nchore: Update .gitlab-ci.yml to print \"Hello, world~\"\n\nSee merge request cloudml-visuals/fe/test!2",
|
||||
"title": "Merge branch 'ci' into 'main'",
|
||||
"timestamp": "2024-07-23T09:28:43+08:00",
|
||||
"url": "https://git.n.xiaomi.com/cloudml-visuals/fe/test/-/commit/66ac20f0f6a839f29753b17dcdf12deb91148fed",
|
||||
"author": {
|
||||
"name": "伍嘉丽",
|
||||
"email": "wujiali5@xiaomi.com"
|
||||
}
|
||||
},
|
||||
"builds": [
|
||||
{
|
||||
"id": 25375438,
|
||||
"stage": "print",
|
||||
"name": "print",
|
||||
"status": "pending",
|
||||
"created_at": "2024-07-23 09:28:44 +0800",
|
||||
"started_at": null,
|
||||
"finished_at": null,
|
||||
"duration": null,
|
||||
"queued_duration": 3.506639987,
|
||||
"when": "on_success",
|
||||
"manual": false,
|
||||
"allow_failure": false,
|
||||
"user": {
|
||||
"id": 30441,
|
||||
"name": "伍嘉丽",
|
||||
"username": "wujiali5",
|
||||
"avatar_url": "https://git.n.xiaomi.com/uploads/-/system/user/avatar/30441/avatar.png",
|
||||
"email": "[REDACTED]"
|
||||
},
|
||||
"runner": null,
|
||||
"artifacts_file": {
|
||||
"filename": null,
|
||||
"size": null
|
||||
},
|
||||
"environment": null
|
||||
}
|
||||
]
|
||||
}
|
@ -1,88 +0,0 @@
|
||||
{
|
||||
"object_kind": "pipeline",
|
||||
"object_attributes": {
|
||||
"id": 8778929,
|
||||
"ref": "ci",
|
||||
"tag": false,
|
||||
"sha": "fc0fdf57c3b662b296b89dc2a289798d130d1be1",
|
||||
"before_sha": "0000000000000000000000000000000000000000",
|
||||
"source": "push",
|
||||
"status": "success",
|
||||
"detailed_status": "passed",
|
||||
"stages": ["print"],
|
||||
"created_at": "2024-07-23 08:57:14 +0800",
|
||||
"finished_at": "2024-07-23 08:57:24 +0800",
|
||||
"duration": 5,
|
||||
"queued_duration": 3,
|
||||
"variables": []
|
||||
},
|
||||
"merge_request": null,
|
||||
"user": {
|
||||
"id": 10011,
|
||||
"name": "赵英博",
|
||||
"username": "zhaoyingbo",
|
||||
"avatar_url": "https://git.n.xiaomi.com/uploads/-/system/user/avatar/10011/avatar.png",
|
||||
"email": "zhaoyingbo@live.cn"
|
||||
},
|
||||
"project": {
|
||||
"id": 145623,
|
||||
"name": "test",
|
||||
"description": "",
|
||||
"web_url": "https://git.n.xiaomi.com/cloudml-visuals/fe/test",
|
||||
"avatar_url": null,
|
||||
"git_ssh_url": "git@git.n.xiaomi.com:cloudml-visuals/fe/test.git",
|
||||
"git_http_url": "https://git.n.xiaomi.com/cloudml-visuals/fe/test.git",
|
||||
"namespace": "fe",
|
||||
"visibility_level": 0,
|
||||
"path_with_namespace": "cloudml-visuals/fe/test",
|
||||
"default_branch": "main",
|
||||
"ci_config_path": ""
|
||||
},
|
||||
"commit": {
|
||||
"id": "fc0fdf57c3b662b296b89dc2a289798d130d1be1",
|
||||
"message": "chore: Add .gitlab-ci.yml for printing \"Hello, world!\"\n",
|
||||
"title": "chore: Add .gitlab-ci.yml for printing \"Hello, world!\"",
|
||||
"timestamp": "2024-07-23T08:57:13+08:00",
|
||||
"url": "https://git.n.xiaomi.com/cloudml-visuals/fe/test/-/commit/fc0fdf57c3b662b296b89dc2a289798d130d1be1",
|
||||
"author": {
|
||||
"name": "zhaoyingbo",
|
||||
"email": "zhaoyingbo@xiaomi.com"
|
||||
}
|
||||
},
|
||||
"builds": [
|
||||
{
|
||||
"id": 25374785,
|
||||
"stage": "print",
|
||||
"name": "print",
|
||||
"status": "success",
|
||||
"created_at": "2024-07-23 08:57:14 +0800",
|
||||
"started_at": "2024-07-23 08:57:18 +0800",
|
||||
"finished_at": "2024-07-23 08:57:24 +0800",
|
||||
"duration": 5.914004,
|
||||
"queued_duration": 3.550911,
|
||||
"when": "on_success",
|
||||
"manual": false,
|
||||
"allow_failure": false,
|
||||
"user": {
|
||||
"id": 10011,
|
||||
"name": "赵英博",
|
||||
"username": "zhaoyingbo",
|
||||
"avatar_url": "https://git.n.xiaomi.com/uploads/-/system/user/avatar/10011/avatar.png",
|
||||
"email": "zhaoyingbo@live.cn"
|
||||
},
|
||||
"runner": {
|
||||
"id": 9134,
|
||||
"description": "cloudml-fe-bj",
|
||||
"runner_type": "group_type",
|
||||
"active": true,
|
||||
"is_shared": false,
|
||||
"tags": ["cloudml-fe-bj"]
|
||||
},
|
||||
"artifacts_file": {
|
||||
"filename": null,
|
||||
"size": null
|
||||
},
|
||||
"environment": null
|
||||
}
|
||||
]
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
import { Gitlab } from "../../types/gitlab"
|
||||
import netTool from "../netTool"
|
||||
import { GITLAB_AUTH_HEADER, GITLAB_BASE_URL, gitlabReqWarp } from "./tools"
|
||||
|
||||
@ -9,7 +10,7 @@ import { GITLAB_AUTH_HEADER, GITLAB_BASE_URL, gitlabReqWarp } from "./tools"
|
||||
*/
|
||||
const getMr = async (project_id: number, sha: string) => {
|
||||
const URL = `${GITLAB_BASE_URL}/projects/${project_id}/repository/commits/${sha}/merge_requests`
|
||||
return gitlabReqWarp<any[]>(
|
||||
return gitlabReqWarp<Gitlab.MergeRequest[]>(
|
||||
() => netTool.get(URL, {}, GITLAB_AUTH_HEADER),
|
||||
[]
|
||||
)
|
||||
|
@ -41,4 +41,18 @@ message.byUserId = async (user_id: string, content: string) => {
|
||||
})
|
||||
}
|
||||
|
||||
message.byUserIdList = async (
|
||||
user_id_list: string[],
|
||||
content: string,
|
||||
api_key?: string
|
||||
) => {
|
||||
return message({
|
||||
receive_id: user_id_list.join(","),
|
||||
receive_id_type: "user_id",
|
||||
msg_type: "interactive",
|
||||
content,
|
||||
api_key,
|
||||
})
|
||||
}
|
||||
|
||||
export default message
|
||||
|
@ -1,12 +1,12 @@
|
||||
export namespace Gitlab {
|
||||
/* 定义错误接口 */
|
||||
/* 错误 */
|
||||
export interface Error {
|
||||
/**
|
||||
* 错误消息
|
||||
*/
|
||||
message: string
|
||||
}
|
||||
/* 定义用户接口 */
|
||||
/* 用户 */
|
||||
export interface User {
|
||||
/**
|
||||
* 用户ID
|
||||
@ -34,7 +34,7 @@ export namespace Gitlab {
|
||||
web_url: string
|
||||
}
|
||||
|
||||
/* 定义项目详情接口 */
|
||||
/* 项目详情 */
|
||||
export interface ProjDetail {
|
||||
/**
|
||||
* 项目ID
|
||||
@ -66,7 +66,7 @@ export namespace Gitlab {
|
||||
message?: string
|
||||
}
|
||||
|
||||
/* 定义管道详情接口 */
|
||||
/* 管道详情 */
|
||||
export interface PipelineDetail {
|
||||
/**
|
||||
* 管道ID
|
||||
@ -114,7 +114,7 @@ export namespace Gitlab {
|
||||
message?: string
|
||||
}
|
||||
|
||||
/* 定义管道接口 */
|
||||
/* 管道 */
|
||||
export interface Pipeline {
|
||||
/**
|
||||
* 管道ID
|
||||
@ -154,7 +154,7 @@ export namespace Gitlab {
|
||||
web_url: string
|
||||
}
|
||||
|
||||
/* 定义徽章接口 */
|
||||
/* 徽章 */
|
||||
export interface Badge {
|
||||
/**
|
||||
* 徽章ID
|
||||
@ -186,12 +186,16 @@ export namespace Gitlab {
|
||||
kind: "project" | "group"
|
||||
}
|
||||
|
||||
/* 定义管道事件接口 */
|
||||
/* 管道事件 */
|
||||
export interface PipelineEvent {
|
||||
/**
|
||||
* 对象属性,包含了构建的详细信息
|
||||
*/
|
||||
object_attributes: {
|
||||
/**
|
||||
* 管道ID
|
||||
*/
|
||||
id: number
|
||||
/**
|
||||
* 分支名
|
||||
*/
|
||||
@ -259,6 +263,60 @@ export namespace Gitlab {
|
||||
* 提交的标题
|
||||
*/
|
||||
title: string
|
||||
/**
|
||||
* 提交的链接URL
|
||||
*/
|
||||
url: string
|
||||
}
|
||||
}
|
||||
|
||||
/* 合并请求 */
|
||||
export interface MergeRequest {
|
||||
/**
|
||||
* 合并请求的标题
|
||||
*/
|
||||
title: string
|
||||
/**
|
||||
* 目标分支
|
||||
*/
|
||||
target_branch: string
|
||||
/**
|
||||
* 源分支
|
||||
*/
|
||||
source_branch: string
|
||||
/**
|
||||
* 作者信息
|
||||
*/
|
||||
author: {
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
username: string
|
||||
/**
|
||||
* 用户姓名
|
||||
*/
|
||||
name: string
|
||||
}
|
||||
/**
|
||||
* 提交的SHA值
|
||||
*/
|
||||
sha: string
|
||||
/**
|
||||
* 合并提交的SHA值
|
||||
*/
|
||||
merge_commit_sha: string
|
||||
/**
|
||||
* 引用信息
|
||||
*/
|
||||
references: {
|
||||
/**
|
||||
* 完整引用
|
||||
*/
|
||||
full: string
|
||||
}
|
||||
/**
|
||||
* 合并请求的web URL
|
||||
*/
|
||||
web_url: string
|
||||
}
|
||||
}
|
||||
|
16
types/notify.ts
Normal file
16
types/notify.ts
Normal file
@ -0,0 +1,16 @@
|
||||
export namespace Notify {
|
||||
export interface CardVariable {
|
||||
project: string
|
||||
project_link: string
|
||||
pipeline: string
|
||||
pipeline_link: string
|
||||
ref: string
|
||||
ref_link: string
|
||||
commit_user: string
|
||||
duration: string
|
||||
participant: string
|
||||
commit_message: string
|
||||
mr: string
|
||||
mr_link: string
|
||||
}
|
||||
}
|
@ -20,3 +20,12 @@ export const getPrevWeekWithYear = () => {
|
||||
export const sec2min = (sec: number) => {
|
||||
return (sec / 60).toFixed(1)
|
||||
}
|
||||
|
||||
/**
|
||||
* 秒转分钟,格式为 1m 30s
|
||||
*/
|
||||
export const sec2minStr = (sec: number) => {
|
||||
const min = Math.floor(sec / 60)
|
||||
const s = sec % 60
|
||||
return `${min}m ${s}s`
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user