[Jenkins 外掛開發] Jenkins 外掛二次開發 - 設計一個程式碼 diff 的小工具
簡介
1:為什麼要開發這個工具
簡要說一下:開發這個Jenkins外掛的初衷是解決公司在程式碼管理上遇到的問題;現狀是:我目前所在的這家公司技術上真的是老古董的那種,程式碼管理水平真的很一般(各種夾帶...),所以不得已才需要做這個外掛協助開發區diff檔案甚至到單行程式碼的變更情況(目前由於技術水平有限,只diff出檔案的變化;我覺得如果diff到具體謀行的變更最難的地方是資料要怎麼展示、這個是一個難點... 資料的獲取可以透過git命令抓取)
2:本文涉及到的 git diff 的簡介
2.1:git 程式碼 diff 的原理
2.1.1:diff 分支之間的檔案的變更
//如下:為diff兩個遠端分支的檔案變更
git diff origin/Release_v1.0 origin/master --stat
diff 之後的結果如下,+ 表示 master 分支相對 Release_v1.0 分支增加的 - 表示 master 分支相對 Release_v1.0 分支刪除的程式碼;兩個分支沒有任何變更的檔案不會展示出來
注意:
1:上述為 diff 兩個遠端分支,如果要 diff 本地分支只有去掉"origin/"即可
2:Git 的賬號、密碼、倉庫 URL 要設定(如果是在拉取的程式碼的工作目錄下這一步可以省略)
2.1.2:diff 分支程式碼行之間的變更
//如下:為diff兩個遠端分支的src/main/java/Core/Filter/xxx.java檔案的變更
git diff origin/Release_v1.0 origin/master src/main/java/Core/Filter/xxx.java
//如下 diff兩個遠端分支src/main/java/Core/Filter資料夾下所有的檔案變更情況
git diff origin/Release_v1.0 origin/master src/main/java/Core/Filter
diff 之後的檔案如下,程式碼行全面標記"-"表示 master 分支相對 Release_v1.0 分支減少的行;"+"表示 master 分支相對 Release_v1.0 分支增加的程式碼行
開始完成這 diff 工具
1:Jenkins 外掛開發的準備工作 - 開始一個 Hello-Word
配置 maven 倉庫的 settings.xml,下面這端匹配 copy 到 maven 的 settings.xml 中
<settings>
<pluginGroups>
<pluginGroup>org.jenkins-ci.tools</pluginGroup>
</pluginGroups>
<profiles>
<!-- Give access to Jenkins plugins -->
<profile>
<id>jenkins</id>
<activation>
<activeByDefault>true</activeByDefault> <!-- change this to false, if you don't like to have it on per default -->
</activation>
<repositories>
<repository>
<id>repo.jenkins-ci.org</id>
<url>http://repo.jenkins-ci.org/public/</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>repo.jenkins-ci.org</id>
<url>http://repo.jenkins-ci.org/public/</url>
</pluginRepository>
</pluginRepositories>
</profile>
</profiles>
<mirrors>
<mirror>
<id>repo.jenkins-ci.org</id>
<url>http://repo.jenkins-ci.org/public/</url>
<mirrorOf>m.g.o-public</mirrorOf>
</mirror>
</mirrors>
</settings>
在指定目錄下執行下列 maven 命令,會自動生成一個 maven 專案,然後倒入 Eclipse 或者 IDEA(推薦)
//your.gound.id 例如:com.jenkins.plugins your.plugin.id 例如:plugins
mvn -U org.jenkins-ci.tools:maven-hpi-plugin:create -DgroupId={your.gound.id} -DartifactId={your.plugin.id}
程式碼結構如下,紅框是自動生成的,pom.xml 也不用修改(如果你需要其他依賴可以直接再匯入)
本地執行,訪問http://localhost:8080/jenkinssay會在"構建"下多一個" hello"的外掛
//本地除錯,預設啟動8080埠
mvn hpi:run
//打包 會在target目錄下生成一個xx.hpi的檔案,我們可以使用這個hpi檔案在jenkins外掛管理中進行本地安裝
mvn clean package
至此 我們已經完成了一個 Jenkins 外掛開發的 Hello Word,下面我們開始實現 Jenkins 外掛的程式碼 diff 功能
2:Jenkins 外掛開發 - 程式碼 diff 外掛
2.1:建立外掛的類方法 需要繼承 Builder 和實現 SimpleBuildStep 介面
import xxx.Jenkins.Plugins.Message.WeChartMess;
import finchina.Jenkins.Plugins.Utils.Excution;
import hudson.Extension;
import hudson.FilePath;
import hudson.Launcher;
import hudson.model.AbstractProject;
import hudson.model.Run;
import hudson.model.TaskListener;
import hudson.tasks.BuildStepDescriptor;
import hudson.tasks.Builder;
import hudson.util.FormValidation;
import jenkins.tasks.SimpleBuildStep;
import net.sf.json.JSONObject;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.StaplerRequest;
import javax.servlet.ServletException;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* 原始碼diff工具
*/
public class SourceDiffBuilder_Git extends Builder implements SimpleBuildStep {
//老分支 非null
private final String consult_Branch;
//新分支 非null
private final String tag_Branch;
//訊息通知
private final String weChartUrl;
private final String atUsers;
/**
* 高階功能部分
* */
//掃描指定目錄下的檔案
private final String tagPaths;
//遮蔽指定條件的檔案或者資料(*.xml:以.xml結尾的;*xml*:檔名稱中包含xml的)
private final String blockFiles;
@DataBoundConstructor
public SourceDiffBuilder_Git(String consult_Branch, String tag_Branch, String weChartUrl, String atUsers , String tagPaths,
String blockFiles) {
this.consult_Branch = consult_Branch;
this.tag_Branch = tag_Branch;
this.atUsers = atUsers;
this.weChartUrl = weChartUrl;
this.tagPaths = tagPaths;
this.blockFiles = blockFiles;
}
@Override
public String toString() {
return "SourceDiffBuilder_Git{" +
"consult_Branch='" + consult_Branch + '\'' +
", tag_Branch='" + tag_Branch + '\'' +
", weChartUrl='" + weChartUrl + '\'' +
", atUsers='" + atUsers + '\'' +
", tagPaths='" + tagPaths + '\'' +
", blockFiles='" + blockFiles + '\'' +
'}';
}
public String getConsult_Branch() {
return consult_Branch;
}
public String getTag_Branch() {
return tag_Branch;
}
public String getWeChartUrl() {
return weChartUrl;
}
public String getAtUsers() {
return atUsers;
}
public String getTagPaths() {
return tagPaths;
}
public String getBlockFiles() {
return blockFiles;
}
@Override
public void perform(Run<?,?> build, FilePath workspace, Launcher launcher, TaskListener listener) throws IOException, InterruptedException {
listener.getLogger().println("get diff parmaters show: "+this.toString());
/**
* demo
*
* 輸出:
* getPreviousBuildUrl = job/demo/8/
* build.getUrl = job/demo/9/
* build.getId = 2020-07-16_11-12-24
* build.getDisplayName = #9
* build.getEnvironment(listener).expand("testEnv") = testEnv
* build.getEnvironment(listener).get("testEnv") = 這是測試驗證
* build.getEnvironment(listener).get("WORKSPACE") = C:\Users\finchina\Desktop\Jenkins\finchina_Plugins\Plugins\work\jobs\demo\workspace
*
* String getPreviousBuildUrl = build.getPreviousBuild().getUrl();
* listener.getLogger().println("getPreviousBuildUrl = "+getPreviousBuildUrl);
* listener.getLogger().println("build.getUrl = "+build.getUrl());
* listener.getLogger().println("build.getId = "+build.getId());
* listener.getLogger().println("build.getDisplayName = "+build.getDisplayName());
* listener.getLogger().println("build.getEnvironment(listener).expand(\"testEnv\") = "+build.getEnvironment(listener).expand("testEnv"));
* listener.getLogger().println("build.getEnvironment(listener).get(\"testEnv\") = "+build.getEnvironment(listener).get("testEnv"));
* listener.getLogger().println("build.getEnvironment(listener).get(\"WORKSPACE\") = "+build.getEnvironment(listener).get("WORKSPACE"));
* */
/**
* 執行業務操作
*
* */
String workSpace = build.getEnvironment(listener).get("WORKSPACE");
List<String> list = Excution.gitDiff(new File(workSpace),consult_Branch,tag_Branch);
String[] strings = atUsers.split(",");
List<String> listatUsers = new ArrayList(Arrays.asList(strings)) ;
/**
* 組裝訊息通知內容
* */
String title = "**請檢視專案:"+build.getEnvironment(listener).get("JOB_NAME")+"的程式碼diff報告**";
String Summary = "***Summary:"+list.get(list.size()-1)+"***";
StringBuffer StringBuffer = new StringBuffer();
for (int i = 0; i < list.size()-1; i++) {
StringBuffer.append("\n").append("> ").append(list.get(i));
}
/**
* 企業微信訊息通知
* */
WeChartMess.actions(weChartUrl,listatUsers,title,Summary,"****Details:**** ",StringBuffer.toString());
}
@Override
public DescriptorImpl getDescriptor() {
return (DescriptorImpl) super.getDescriptor();
}
@Extension
public static final class DescriptorImpl extends BuildStepDescriptor<Builder> {
public DescriptorImpl() {
load();
}
public FormValidation doCheckName(@QueryParameter String consult_Branch , @QueryParameter String tag_Branch)
throws IOException, ServletException {
if (consult_Branch.length() == 0 && tag_Branch.length() == 0)
return FormValidation.error("Please set a oldBranch and newBranch");
if (consult_Branch.length() < 4 && tag_Branch.length() < 4)
return FormValidation.warning("Isn't the oldBranch and newBranch too short?");
return FormValidation.ok();
}
public boolean isApplicable(Class<? extends AbstractProject> aClass) {
return true;
}
/**
* 外掛的名稱
*/
public String getDisplayName() {
return "原始碼Diff工具_Git";
}
@Override
public boolean configure(StaplerRequest req, JSONObject formData) throws FormException {
save();
return super.configure(req,formData);
}
}
}
2.2:執行 diff git 命令的類方法的封裝
import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
/**
* 執行命令
* */
public class Excution {
/**
* diff操作後獲取執行命令的結果
* @param workspace 程式碼工作空間
* @param oldBranch diff的比較/參照分支
* @param fleashBranch 需要diff的分支
* @return List<String> 執行結果的List集合
* */
public static List<String> gitDiff(File workspace,String oldBranch,String fleashBranch){
/**
* 1:進入到workspace
* 2:執行git命令獲取資料
* 3:封裝資料為list集合
* */
String cmd = "git diff "+oldBranch+" "+fleashBranch+" --stat";
return exeCmd(cmd,workspace);
}
/**
* 執行linux命令 獲取返回值組裝成集合
* */
public static List<String> exeCmd(String commandStr, File workspace) {
List<String> list = new ArrayList();
try {
Process ps = Runtime.getRuntime().exec(commandStr,null,workspace);
BufferedReader br = new BufferedReader(new InputStreamReader( ps.getInputStream(), Charset.forName("UTF-8")));
String line;
while ((line = br.readLine()) != null) {
list.add(line);
}
} catch (Exception e) {
e.printStackTrace();
}
return list;
}
}
2.3:企業微信訊息通知 (程式碼 diff 結束將會傳送訊息給對應的使用者)
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import okhttp3.*;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.TimeUnit;
/**
* 固定訊息通知格式為markdown格式 其他的可以參考企業微信開發文件
* */
public class WeChartMess {
private static Object type = "markdown";
public static String sendWeChartNotices (String reqBody,String url) throws IOException {
OkHttpClient client = new OkHttpClient.Builder().connectTimeout(10, TimeUnit.SECONDS)// 設定連線超時時間
.readTimeout(20, TimeUnit.SECONDS)// 設定讀取超時時間
.build();
MediaType contentType = MediaType.parse("application/json; charset=utf-8");
RequestBody body = RequestBody.create(contentType, reqBody);
Request request = new Request.Builder().url(url).post(body).addHeader("cache-control", "no-cache").build();
Response response = client.newCall(request).execute();
byte[] datas = response.body().bytes();
String respMsg = new String(datas);
return respMsg;
}
/**
* Map -> json
*
* 建議使用LinkedHashMap,因為LinkedHashMap有順序
* */
public static String mapToJson(Map<String , Object> map){
return JSON.toJSONString(map);
}
/**
* List->String
* */
public static String listToString(List<String> list){
StringBuffer stringBuffer = new StringBuffer();
for (int i = 0; i < list.size(); i++) {
if(i != (list.size() -1)){
stringBuffer.append(list.get(i)+"\n");
}else{
stringBuffer.append(list.get(i));
}
}
return stringBuffer.toString();
}
/**
* 執行釋出訊息的操作
* */
public static String actions(String url,List<String> atUsers,String... content) throws IOException {
Map<String , Object> map = new LinkedHashMap();
Map<String , Object> text = new LinkedHashMap();
//list轉String
List<String> list = Arrays.asList(content);
String str = listToString(list);
map.put("msgtype", type);
//拼接@xx的操作
StringBuffer buffer = new StringBuffer();
//@使用者的操作,一般情況下企業微信的UserId就是公司郵箱的字首
for(String atUserId : atUsers){
if(StrUtil.isNotEmpty(atUserId)){
buffer.append("<@");
buffer.append(atUserId);
buffer.append(">");
}
text.put("content",str+"\n"+buffer);
}
map.put(type.toString(),text);
String resBody = sendWeChartNotices(mapToJson(map),url);
return resBody;
}
}
2.4:完成 jelly 檔案的配置,進行介面的引數化
- 對於開發這作用於構建過程中的外掛只有修改 config.jelly 檔案即可 ###### 2.4.1:jelly 檔案的簡單介紹
- jelly 檔案類似於 html 檔案,但是他跟 html 又有很大的不同,在 Jenkins 外掛開發過程中可以使用 Groovy 代替 jelly(這裡我還在研究中,有懂的可以分享出來,目前 Jenkins 外掛開發的中文資料還是比較少的)
- texbox
//textbox:表示是一個文字輸入框
<f:entry title="Consult_Branch" field="consult_Branch" description="上一個master分支或者釋出分支">
<f:textbox />
</f:entry>
- password:表示是一個密碼框,內容會以密文展示
<f:entry title="Tag_Branch" field="tag_Branch" description="當前釋出的Release分支或者釋出分支">
<f:password />
</f:entry>
- advanced:標記的會展示在"高階"小,預設摺疊,點選"高階"會展開
<f:advanced>
<f:entry title="BlockFiles" field="blockFiles" description="高階功能:黑名單(加入黑名單的檔案將不會被diff 格式:*diff*、*.xml...)">
<f:textbox />
</f:entry>
<f:entry title="TagPaths" field="tagPaths" description="高階功能:目標paths">
<f:textbox />
</f:entry>
</f:advanced>
2.4.2: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="Consult_Branch" field="consult_Branch" description="上一個master分支或者釋出分支">
<f:textbox />
</f:entry>
<f:entry title="Tag_Branch" field="tag_Branch" description="當前釋出的Release分支或者釋出分支">
<f:textbox />
</f:entry>
<f:entry title="WeChartUrl" field="weChartUrl" description="企業微信訊息通知url連結(包含token)">
<f:textbox />
</f:entry>
<f:entry title="@Users" field="atUsers" description="企業微信群@操作">
<f:textbox />
</f:entry>
<!-- 高階功能部分 -->
<f:advanced>
<f:entry title="BlockFiles" field="blockFiles" description="高階功能:黑名單(加入黑名單的檔案將不會被diff 格式:*diff*、*.xml...)">
<f:textbox />
</f:entry>
<f:entry title="TagPaths" field="tagPaths" description="高階功能:目標paths">
<f:textbox />
</f:entry>
</f:advanced>
</j:jelly>
2.4.3:help.html 檔案
- 命名規則是 help-field.html
<div>
atUser:使用者可以輸入企業微信使用者ID,在當前構建步驟結束後會通知到對應的企業微信群並@相關人員
示例:zhangshang,lishi,wangwu
</div>
2.4.4:mvn hpi:run 本地檢視外掛開發情況
* 如下:是外掛的 style 展示
* 企業微信通知效果如下
相關文章
- 安裝Jenkins常用外掛Jenkins
- Jenkins 外掛開發之旅:兩天內從 idea 到釋出(上篇)JenkinsIdea
- Jenkins 外掛開發之旅:兩天內從 idea 到釋出(下篇)JenkinsIdea
- jenkins離線外掛下載Jenkins
- Jenkins外掛安裝失敗Jenkins
- Jenkins 安裝外掛慢,提速Jenkins
- 從頭開發一個Flutter外掛(一)開發流程Flutter
- chrome 外掛開發Chrome
- flutter 外掛開發Flutter
- 開發Rhino外掛
- VscodeIDEA開發外掛VSCodeIdea
- Skywalking 外掛開發
- Webstorm 外掛開發WebORM
- Flutter外掛開發Flutter
- Mybatis外掛開發MyBatis
- 小程式外掛的開發和使用
- ZBlog外掛開發檔案結構(外掛)
- apisix~lua外掛開發與外掛註冊API
- InDesign外掛--常規功能開發--隨機填充--js指令碼開發--ID外掛隨機JS指令碼
- wordpress外掛開發03-簡單的all in one seo 外掛開發
- AndroidStudio外掛GsonFormat解析及二次開發AndroidORM
- 【轉】jenkins外掛pipeline使用介紹Jenkins
- Jenkins 自動化安裝外掛Jenkins
- 從頭開發一個Flutter外掛(二)高德地圖定位外掛Flutter地圖
- vue開發的一些外掛Vue
- go~wasm外掛的開發GoASM
- 文心一言外掛設計與開發
- dlopen開發外掛庫
- Chrome外掛開發教程Chrome
- eslint外掛開發教程EsLint
- Eclipse外掛開發demoEclipse
- Maven外掛開發教程Maven
- Idea日常開發外掛Idea
- VScode股票外掛開發VSCode
- babel 外掛開發案例Babel
- Vue-外掛開發Vue
- babel外掛開發心得Babel
- [外掛擴充套件]基於PHPMailer開發的一個判斷失誤的外掛....套件PHPAI