Jenkins推送设置
# 推送钉钉
# 构建信息推送钉钉(个人定制)
# 结合 Gitlab 和钉钉的自定义机器人(webhook 触发)的一个推送封装
# 流程
一步到位,不需要去关注其他,也不用占用其他人的时间来帮你定位一些很基础的信息。
# 实现的功能
提供跳转到对应的 gitlab 仓库(包含issue 这些)
判断根目录是否有 changelog,有则提供跳转 gitlab 对应的 changelog 文件
谁推送了,推送的行为
快速跳转到对应的 jenkins-job,查看构建过程
输出仓库的概要信息
- 输出仓库的名字
- 获取最近五次提交的 commit 概要,忽略 merge request 这些的
- 展示构建的那次 commit 并支持跳转到 gitlab 查看该 commit 的变动记录
- 展示构建的分支并支持跳转到 gitlab 的分支
- 支持文档链接传入
支持同时把信息推送给多个群
没有用到第三方库,都是用 node 的内置 api 实现文件读取操作及 http 请求。
构建推送的效果
- 蓝色区域都是可以点击快速跳转到对应的访问区域;
- 打包的版本号仅在 master 和 dev 才有可能出现,对应 npm 的 dist-tags 的 latest 和 dev
- banner 不传是没有顶部的图片的,也支持文本的传入或者标准的 markdown 字符串
稳定版本:
开发版本:
# 实现的过程
# 配置读取
如何读取执行根目录的配置文件呢?
主要用到了 process.cwd
查询执行路径, 实现读取 package.json 和独立配置文件的参数
const fs = require("fs");
const path = require("path");
const process = require("process");
const jk2dtFile = path.resolve(process.cwd(), "./jk2dtrc.js");
const pkgFile = require(path.resolve(process.cwd(), "./package.json"));
let importConfig = {};
if (fs.existsSync(jk2dtFile)) {
process.stdout.write("jk2dt配置文件存在 \n");
const config = require(jk2dtFile);
importConfig = config;
} else {
if (fs.existsSync(pkgFile)) {
process.stdout.write("jk2dt配置文件不存在,尝试从 package.json 读取 \n");
if (pkgFile.jk2dt && typeof pkgFile.jk2dt === "object") {
importConfig = pkgFile.jk2dt;
} else {
process.stdout.write("package.json也没有对应配置项,采用默认配置 \n");
}
}
}
module.exports = importConfig;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# markdown自定义模版转换
在 markdown 里面提供一些占位符,来达到定制化的效果,最简单粗暴的模板替换的姿势
us-msg.md
{{TIPS_BANNER}}
{{GitRepoDesc}}
### --- {{GitRepoName}} ---
**构建分支:** {{GitRepoBranchUrl}}
{{PkgVersion}}
{{GitBuildCommitLink}}
{{GitRepoChangeLog}}
{{RepoRecentTitle}}
{{RepoRecentCommitMsg}}
{{GitRepoActionType}}
### --- Jenkins ---
**执行人:** {{PushBy}}
**构建任务:** {{JK_JOBS_NAME}}
**构建日志:** {{JK_JOBS_CONSOLE}}
**构建状态:** {{JK_JOBS_STATUS}}
**构建时间:** {{JK_JOBS_TIME}}
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
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
covert-md-2-str
const fs = require("fs");
const path = require("path");
function mdTemplateStr({
TipsBanner,
TemplateName,
PkgVersion,
JobInfo: {
JOB_NAME,
JOB_BUILD_DISPLAY_NAME,
JOB_BUILD_URL,
JOB_STATUS,
JOB_END_TIME
},
GitInfo: {
RepoUrl,
RepoBranch,
RepoName,
RepoDesc,
RepoBranchUrl,
RepoChangeLog,
RepoPushMan,
RepoActionType,
RepoRecentCommitMsg,
BuildCommitMDLink
}
}) {
const PlacehoderVar = {
"{{TIPS_BANNER}}": TipsBanner,
"{{JK_JOBS_NAME}}": JOB_NAME,
"{{JK_JOBS_TIME}}": JOB_END_TIME,
"{{JK_JOBS_CONSOLE}}": `[${JOB_BUILD_DISPLAY_NAME}](${JOB_BUILD_URL})`,
"{{JK_JOBS_STATUS}}": JOB_STATUS,
"{{GitRepoName}}": RepoName,
"{{GitRepoDesc}}": RepoDesc,
"{{GitRepoBranch}}": RepoBranch,
"{{GitRepoBranchUrl}}": RepoBranchUrl,
"{{PkgVersion}}": PkgVersion ? `**打包版本:** ${PkgVersion}` : "",
"{{RepoRecentTitle}}": RepoRecentCommitMsg ? "**提交概要:**" : "",
"{{RepoRecentCommitMsg}}": RepoRecentCommitMsg,
"{{GitRepoChangeLog}}":
RepoBranch === "master"
? `**变更日志:** [CHANGELOG](${RepoChangeLog})`
: "",
"{{GitBuildCommitLink}}": BuildCommitMDLink
? `**构建提交:** ${BuildCommitMDLink}`
: "",
"{{GitRepoActionType}}": RepoActionType
? `**推送行为:** ${RepoActionType}`
: "",
"{{PushBy}}": RepoPushMan
};
let mdStr = fs.readFileSync(
path.join(__dirname, `../template/${TemplateName}.md`)
);
mdStr = mdStr.toString();
for (const [k, v] of Object.entries(PlacehoderVar)) {
const re = new RegExp(k, "g");
mdStr = mdStr.replace(re, v);
}
return mdStr;
}
module.exports = mdTemplateStr;
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
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
# 查询 changelog 文件是否存在
- 先判断项目根目录是否存在对应的CHANGELOG.md
- 没有再用 Linux 的
grep
查询 changlog.md(忽略大小写),用execSync
同步执行 shell
const path = require("path");
const pkgFile = require(path.resolve(process.cwd(), "./package.json"));
const projectExecShellPath = process.cwd();
const fs = require("fs");
const { execSync } = require("child_process");
function rootExistChangelogFile() {
const CHANGELOG = path.resolve(process.cwd(), "./CHANGELOG.md");
try {
if (fs.existsSync(CHANGELOG)) {
return true;
} else {
return !!execSync(
`ls -l ${projectExecShellPath} | grep -i "changelog.md"`
).toString();
}
return false;
} catch (error) {
return false;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 查询npm包的版本
- 先判断是否包含 package.name 或者 main(主入口)是否存在,这是包的必要因素
- 然后判断是否我们考虑的分支范围
- 最后 shell 去查询
/**
* 获取包的dist-tags
*/
function getPackageDistTag(branch) {
if (
!pkgFile ||
!pkgFile.name ||
!pkgFile.main ||
["master", "dev", "develop", "next"].indexOf(branch) === -1
) {
return "";
}
let distTag;
switch (branch) {
case "master":
distTag = "latest";
break;
case "dev":
distTag = "dev";
break;
case "develop":
distTag = "dev";
break;
case "next":
distTag = "next";
break;
default:
distTag = "dev";
break;
}
const execShell = `npm show ${pkgFile.name} dist-tags.${distTag} 2>/dev/null`;
try {
return execSync(execShell).toString();
} catch (error) {
return "";
}
}
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
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
# 获取最近的五次提交概要
- 小于等于0的默认及分支不存在当做不查询
- 用
grep
忽略包含 "lerna|into|merge"词汇的提交概要 - 再用 sed 来改造字符串,输出一个带换行的 markdown 格式字符串
function getLastNCommit(n = 5, branch) {
if (n <= 0 || !branch) {
return "";
}
const readLineFilterResult =
branch === "master"
? 'grep -E -i -v "lerna"'
: 'grep -E -i -v "lerna|into|merge"';
const lineModify = "sed 's/^/> /g' |sed 's/$/\\\n/g' ";
const execShell = `git log --oneline -${n} ${branch}| ${readLineFilterResult} | ${lineModify} `;
try {
return execSync(execShell).toString();
} catch (error) {
return "";
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 判断accessToken是否有效
支持字符串和数组,强校验
function findValidAK(dict) {
let tempObj = {};
for (let [k, v] of Object.entries(dict)) {
if (isType.isString(v) && v) {
tempObj[k] = v;
}
if (
isType.isObj(v) &&
Array.isArray(v.success) &&
Array.isArray(v.error) &&
v.success.length > 0 &&
v.error.length > 0
) {
tempObj[k] = v;
}
}
return tempObj;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 纯粹的类型判断
function isThenable(obj) {
return (
!!obj &&
(typeof obj === "object" || typeof obj === "function") &&
typeof obj.then === "function"
);
}
function isString(o) {
return Object.prototype.toString.call(o).slice(8, -1) === "String";
}
function isNumber(o) {
return Object.prototype.toString.call(o).slice(8, -1) === "Number";
}
function isObj(o) {
return Object.prototype.toString.call(o).slice(8, -1) === "Object";
}
module.exports = {
isThenable,
isString,
isObj,
isNumber
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 注意事项
- 仅支持linux/unix/macos,调用了一些很常见的命令行, 如 grep,ls ;
- 和 jenkins 高度耦合,很多基础信息都是从 jenkins 内置临时环境变量拿的;
- 仓库信息基本所有基础信息支持覆写, 默认从 jenkins 提供的临时环境变量构建的Git仓库信息;
# 推送企业微信
上次更新: 2022/04/15, 05:41:30