首页 » 软件开发 » 持续交付之解决Jenkins集成编译获取代码提交记录及钉钉通知(编译插件获取通知代码)

持续交付之解决Jenkins集成编译获取代码提交记录及钉钉通知(编译插件获取通知代码)

雨夜梧桐 2024-07-25 03:55:01 0

扫一扫用手机浏览

文章目录 [+]

落地方案:

Jenkins Changelog Environment Plugin 实现获取代码提交记录使用 Python 获取代码提交记录并推送钉钉通知1、下载插件源码

Git Clone https://github.com/daniel-beck/changelog-environment-plugin 插件源码

2、源码简要分析

代码骨架结构如下:

持续交付之解决Jenkins集成编译获取代码提交记录及钉钉通知(编译插件获取通知代码) 软件开发
(图片来自网络侵删)

└─src ├─main │ ├─java │ │ └─org │ │ └─jenkinsci │ │ └─plugins │ │ └─changelogenvironment │ │ ChangelogEnvironmentContributor.java # 插件具体实现类 │ │ │ └─resources │ │ index.jelly # 插件的概要描述,可以在插件列表中看到 │ │ │ └─org │ └─jenkinsci │ └─plugins │ └─changelogenvironment │ │ Messages.properties #GUI配置文件 │ │ Messages_de.properties #GUI配置文件 │ │ │ └─ChangelogEnvironmentContributor │ config.jelly # 插件在单个job中的配置 │ config_de.properties # 配置文件 │ help-dateFormat.html # jelly文件中属性的帮助文件 │ help-dateFormat_de.html # 同上 │ help-entryFormat.html # 同上 │ help-entryFormat_de.html # 同上 │ help-lineFormat.html # 同上 │ help-lineFormat_de.html # 同上 │ └─test └─java └─org └─jenkinsci └─plugins └─changelogenvironment ChangelogEnvironmentContributorTest.java # 单元测试类│ pom.xml

index.jelly: 插件的概要描述,可以在插件列表中看到

<?jelly escape-by-default='true'?><div> This plugin allows you to add changelog information to the build environment for use in build scripts.</div>

Jenkins 展示效果:

pom.xml:name 属性值就是插件管理页面中的插件名称,如下:

<name>Changelog Environment Plugin</name>

html:jelly文件中属性的帮助文件,点击插件的“?”即可展示:

<p>This field specifies the Java <code>String</code> format of the changelog entry header. It accepts four <code>String</code> placeholders: <ol> <li>The author of the commit</li> <li>The ID or revision of the commit</li> <li>The commit message</li> <li>The date of the commit in the format specified in the <em>Date Format</em> field</li> </ol> After this commit information, a list of affected paths will be printed in the <em>File Item Format</em>.</p>

帮助文件在 Jenkins GUI 效果如下:

接下来我们来看下 ChangelogEnvironmentContributor.java 类

public class ChangelogEnvironmentContributor extends SimpleBuildWrapper { private String entryFormat; private String lineFormat; private String dateFormat; @DataBoundConstructor public ChangelogEnvironmentContributor() { // need empty constructor so Stapler creates instances } @DataBoundSetter public void setDateFormat(String dateFormat) { this.dateFormat = dateFormat; } @DataBoundSetter public void setEntryFormat(String entryFormat) { this.entryFormat = entryFormat; } @DataBoundSetter public void setLineFormat(String lineFormat) { this.lineFormat = lineFormat; } public String getEntryFormat() { return this.entryFormat; } public String getLineFormat() { return this.lineFormat; } public String getDateFormat() { return this.dateFormat; } @Override public void setUp(Context context, Run<?, ?> build, FilePath workspace, Launcher launcher, TaskListener listener, EnvVars initialEnvironment) throws IOException, InterruptedException { StringBuilder sb = new StringBuilder(); DateFormat df; try { df = new SimpleDateFormat(Util.fixNull(dateFormat)); } catch (IllegalArgumentException ex) { listener.error("Failed to insert changelog into the environment: Illegal date format"); return; } try { if (build instanceof AbstractBuild<?, ?>) { AbstractBuild<?, ?> abstractBuild = (AbstractBuild<?, ?>) build; ChangeLogSet cs = abstractBuild.getChangeSet(); processChangeLogSet(sb, cs, df); } try { // FIXME TODO I have no idea whether this works, untested if (build instanceof WorkflowRun) { WorkflowRun wfr = (WorkflowRun) build; List<ChangeLogSet<? extends ChangeLogSet.Entry>> changeLogSets = wfr.getChangeSets(); for (ChangeLogSet<? extends ChangeLogSet.Entry> changeLogSet : changeLogSets) { processChangeLogSet(sb, changeLogSet, df); } } } catch (NoClassDefFoundError ncder) { // ignore } } catch (IllegalFormatException ex) { listener.error("Failed to insert changelog into the environment: " + ex.getMessage()); return; } String value = sb.toString(); if (!"".equals(value)) { context.env("SCM_CHANGELOG", value); } } private void processChangeLogSet(StringBuilder sb, ChangeLogSet cs, DateFormat df) { for (Object o : cs) { ChangeLogSet.Entry e = (ChangeLogSet.Entry) o; sb.append(String.format(Util.fixNull(this.entryFormat), e.getAuthor(), e.getCommitId(), e.getMsg(), df.format(new Date(e.getTimestamp())))); try { for (ChangeLogSet.AffectedFile file : e.getAffectedFiles()) { sb.append(String.format(Util.fixNull(this.lineFormat), file.getEditType().getName(), file.getPath())); } } catch (UnsupportedOperationException ex) { // early versions of SCM did not support getAffectedFiles, only had getAffectedPaths for (String file : e.getAffectedPaths()) { sb.append(String.format(Util.fixNull(this.lineFormat), "", file)); } } } } @Extension public static class ChangelogEnvironmentContributorDescriptor extends BuildWrapperDescriptor { @Override public boolean isApplicable(AbstractProject<?, ?> item) { // only really makes sense for jobs with SCM, but cannot really not show this option otherwise // users would have to leave the config form between setting up SCM and this. return true; } @Override public String getDisplayName() { return Messages.DisplayName(); } public FormValidation doCheckLineFormat(@QueryParameter String lineFormat) { try { String result = String.format(lineFormat, "add", "README.md"); return FormValidation.ok(Messages.LineFormat_Sample(result)); } catch (IllegalFormatException ex) { return FormValidation.error(Messages.LineFormat_Error()); } } public FormValidation doCheckEntryFormat(@QueryParameter String entryFormat) { try { String result = String.format(entryFormat, "danielbeck", "879e6fa97d79fd", "Initial commit", 1448305200000L); return FormValidation.ok(Messages.EntryFormat_Sample(result)); } catch (IllegalFormatException ex) { return FormValidation.error(Messages.EntryFormat_Error()); } } public FormValidation doCheckDateFormat(@QueryParameter String dateFormat) { try { String result = new SimpleDateFormat(dateFormat).format(new Date(1448305200000L)); return FormValidation.ok(Messages.DateFormat_Sample(result)); } catch (IllegalArgumentException ex) { return FormValidation.error(Messages.DateFormat_Error()); } } }}ChangelogEnvironmentContributor:继承 SimpleBuildWrapper,是 BuildWrapper 的一个公共抽象类,其是可扩展点,用于执行构建过程的前 / 后操作,比如准备构建的环境,设置环境变量等实现 setUp 方法,主要为获取代码提交记录,几个参数: Context context:当前上下文Run build:当前构建任务FilePath workspace:文件路径Launcher launcher:启动构建TaskListener listener:检查构建状态EnvVars initialEnvironment:环境变量@DataBoundConstructor:插件的构造函数,此处留空以便 Stapler 创建实例@DataBoundSetter:标识插件属性的 setter 方法@Extension:扩展点注释,Jenkins 通过此注解自动发现扩展点,并加入扩展列表

判断构建是否为基本实例,AbstractBuild 是基本构建实例

if (build instanceof AbstractBuild<?, ?>) { AbstractBuild<?, ?> abstractBuild = (AbstractBuild<?, ?>) build; ChangeLogSet cs = abstractBuild.getChangeSet(); processChangeLogSet(sb, cs, df); }

获取合并到当前构建中的所有更改信息

ChangeLogSet cs = abstractBuild.getChangeSet();

判断构建是否为工作流实例,WorkflowRun 是工作流实例,类似 pipeline?(此处没有查到 API),这里作者说可能失效,未经过验证。

// FIXME TODO I have no idea whether this works, untested if (build instanceof WorkflowRun) { WorkflowRun wfr = (WorkflowRun) build; List<ChangeLogSet<? extends ChangeLogSet.Entry>> changeLogSets = wfr.getChangeSets(); for (ChangeLogSet<? extends ChangeLogSet.Entry> changeLogSet : changeLogSets) { processChangeLogSet(sb, changeLogSet, df); } }

getChangeSets 为获取当前与此项关联的所有更改日志

List<ChangeLogSet<? extends ChangeLogSet.Entry>> changeLogSets = wfr.getChangeSets();

processChangeLogSet:自定义处理当前更改日志,主要为格式化和拼接日志

private void processChangeLogSet(StringBuilder sb, ChangeLogSet cs, DateFormat df) { for (Object o : cs) { ChangeLogSet.Entry e = (ChangeLogSet.Entry) o; sb.append(String.format(Util.fixNull(this.entryFormat), e.getAuthor(), e.getCommitId(), e.getMsg(), df.format(new Date(e.getTimestamp())))); try { for (ChangeLogSet.AffectedFile file : e.getAffectedFiles()) { sb.append(String.format(Util.fixNull(this.lineFormat), file.getEditType().getName(), file.getPath())); } } catch (UnsupportedOperationException ex) { // early versions of SCM did not support getAffectedFiles, only had getAffectedPaths for (String file : e.getAffectedPaths()) { sb.append(String.format(Util.fixNull(this.lineFormat), "", file)); } } } }

最后变更日志赋值给 SCM_CHANGELOG 变量

String value = sb.toString(); if (!"".equals(value)) { context.env("SCM_CHANGELOG", value); }

ChangelogEnvironmentContributorDescriptor:配置类,这里主要对GUI输入进行格式验证,通过 config.jelly 配置文件进行参数设置:

<?jelly escape-by-default='true'?><j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form"> <f:entry title="${%Entry Format}" field="entryFormat"> <f:textarea/> </f:entry> <f:entry title="${%File Item Format}" field="lineFormat"> <f:textarea/> </f:entry> <f:entry title="${%Date Format}" field="dateFormat"> <f:textbox/> </f:entry></j:jelly>doCheckLineFormat:文件验证doCheckEntryFormat:变更日志验证doCheckDateFormat:时间格式验证3、编译源码生成 hpi

切换到程序根目录 changelog-environment-plugin-master 下,执行:

mvn verify

等待一段比较长的时间,会在 ./target/ 下生成一个 changelog-environment.hpi 文件,这个就是我们需要的插件

把生成的插件文件上传到 Jenkins

4、Jenkins Job设置

项目的配置中,构建环境下面多了一项Add Changelog Information to Environment,如下图:

Entry Format :添加 - %3$s (%4$s %1$s) ,参数分别为 ChangeLog、时间、提交人Date Format :添加 yyyy-MM-dd HH:mm:ss 就是时间格式

顺序依据如下:

sb.append(String.format(Util.fixNull(this.entryFormat), e.getAuthor(), e.getCommitId(), e.getMsg(), df.format(new Date(e.getTimestamp()))));5、钉钉通知

通过 os.getenv("SCM_CHANGELOG") 获取变更日志内容

完整 Python 脚本如下:

# coding=utf-8'''@author: zuozewei@file: notification.py@time: 2019/4/25 18:00@description:dingTalk通知类'''import os, jenkins, configparser, requests, json, timefrom dingtalkchatbot.chatbot import DingtalkChatbotfrom jsonpath import jsonpath# 获取Jenkins变量JOB_NAME = str(os.getenv("JOB_NAME"))BUILD_URL = str(os.getenv("BUILD_URL")) + "console"BUILD_VERSION = str(os.getenv("BUILD_VERSION"))JENKINS_HOME = os.getenv("JENKINS_HOME")BUILD_NUMBER = str(os.getenv("BUILD_NUMBER"))SCM_CHANGELOG = str(os.getenv("SCM_CHANGELOG"))WORKSPACE = os.getenv("WORKSPACE")versionPath = JENKINS_HOME + "\workspace\Version.ini"# 读取版本号config = configparser.ConfigParser()config.read(versionPath)xxx_Major = config.get("xxx", "xxx_Major")xxx_Minor = config.get("xxx", "xxx_Minor")xxx_Build = config.get("xxx", "xxx_Build")xxx_Revision = config.get("xxx", "xxx_Revision")VERSION = xxx_Major + "." + xxx_Minor + "." + xxx_Build+ "." + xxx_Revision # 判断日志内容if SCM_CHANGELOG == 'None': SCM_CHANGELOG = '- No changes' print("empty")else: print("not empty") passdef buildNotification(): title = 'xxx编译通知' # 连接jenkins server1 = jenkins.Jenkins(url="http://xxxx.xxx.xxx.xxx:8080", username='xxx', password="xxx") build_info = server1.get_build_info(JOB_NAME, int(BUILD_NUMBER)) # dict字典转json数据 build_info_json = json.dumps(build_info) # 把json字符串转json对象 build_info_jsonobj = json.loads(build_info_json) # 获取任务触发原因 causes = jsonpath(build_info_jsonobj, '$.actions..shortDescription') print(causes[0]) textFail = '#### ' + JOB_NAME + ' - Build # ' + BUILD_NUMBER + ' \n' + \ '##### <font color=#FF0000 size=6 face="黑体">编译状态: ' + BUILD_STATUS + '</font> \n' + \ '##### 版本类型: ' + '开发版' + '\n' + \ '##### 当前版本: ' + VERSION + '\n' + \ '##### 触发类型: ' + str(causes[0]) + '\n' + \ '##### 编译日志: [查看详情](' + BUILD_URL + ') \n' + \ '##### 关注人: @186xxxx2487 \n' + \ '##### 更新记录: \n' + \ SCM_CHANGELOG + '\n' + \ '> ###### xxx技术团队 \n ' textSuccess = '#### ' + JOB_NAME + ' - Build # ' + BUILD_NUMBER + ' \n' + \ '##### 编译状态: ' + BUILD_STATUS + '\n' + \ '##### 版本类型: ' + '开发版' + '\n' + \ '##### 当前版本: ' + VERSION + '\n' + \ '##### 触发类型: ' + str(causes[0]) + '\n' + \ '##### 编译日志: [查看详情](' + BUILD_URL + ') \n' + \ '##### 更新记录: \n' + \ SCM_CHANGELOG + '\n' + \ '> ###### xxx技术团队 \n ' if BUILD_STATUS == 'SUCCESS': dingText = textSuccess else: dingText = textFail sendding(title, dingText)def sendding(title, content): at_mobiles = ['186xxxx2487'] Dingtalk_access_token = 'https://oapi.dingtalk.com/robot/send?access_token=xxxxx' # 初始化机器人小丁 xiaoding = DingtalkChatbot(Dingtalk_access_token) # Markdown消息@指定用户 xiaoding.send_markdown(title=title, text=content, at_mobiles=at_mobiles) if __name__ == "__main__": buildNotification()

最终几种通知的效果如下:

三、小结

本文带着大家从 Jenkins 插件源码到应用走了一遍,可能有些地方描述还不是很具体,不过针对大部分的 Jenkins 插件,这个套路是类似的,希望大家能有所启发。

本文源码:

https://github.com/zuozewei/blog-example/tree/master/Jenkins-ci/jenkins-changeLog-dingtalk-notifications
标签:

相关文章

语言中的借用,文化交融的桥梁

自古以来,人类社会的交流与发展离不开语言的传播。在漫长的历史长河中,各民族、各地区之间的文化相互碰撞、交融,产生了许多独特的语言现...

软件开发 2025-01-01 阅读1 评论0

机顶盒协议,守护数字生活的新卫士

随着科技的飞速发展,数字家庭逐渐走进千家万户。在这个时代,机顶盒成为了连接我们与丰富多彩的数字世界的重要桥梁。而机顶盒协议,作为保...

软件开发 2025-01-01 阅读1 评论0

语言基础在现代社会的重要性及方法步骤

语言是人类沟通的桥梁,是社会发展的基础。语言基础作为语言学习的基石,对于个人、社会乃至国家的发展具有重要意义。本文将从语言基础在现...

软件开发 2025-01-01 阅读4 评论0

粤语电影,传承文化,点亮时代之光

粤语电影,作为中国电影产业的一朵奇葩,以其独特的地域特色、丰富的文化内涵和鲜明的艺术风格,赢得了广大观众的喜爱。本文将从粤语电影的...

软件开发 2025-01-01 阅读9 评论0

苹果游戏语言,塑造未来娱乐体验的基石

随着科技的飞速发展,游戏产业逐渐成为全球娱乐市场的重要支柱。在我国,游戏产业更是蓬勃发展,吸引了无数玩家和投资者的目光。而在这其中...

软件开发 2025-01-01 阅读1 评论0