需求来源:近公司要求项目中对接jenkins,使用jenkins与jira交互,将项目中扫描出的漏洞自动在Jira上生成Bug工单,指定人员去修复,之前项目我也没参与,这两个工具也没使用过,但要求一周内完成对接,着实焦虑,下面是在开发过程中的一些步骤,希望能给其他同行少走一点弯路。
本文相关工具下载 windows+jdk.18+jenkins+jira+java+cmd+jira自动生成问题工单-Java文档类资源-CSDN文库https://download.csdn.net/download/qq_31497435/86831955
一:在windows系统上安装jdk1.8(如何安装自行摸索)
二:安装Jira
① 双击exe文件,打开后,点击next
② 选择Custom Install,next
④. 设置home文件夹路径 next
⑤. next
⑥. 修改默认端口 next
⑦. next
⑧ . next
⑨. 安装中,等待...
⑩ 安装完成 next
⑩①. 软件启动中,稍等...
⑩②. 完成,点击Finish,将会自动打开Jira网页
三:Jira破解
①. 关闭Jira软件
②. 将破解的jar包与mysql数据库的连接jar包,放置安装目录中,是在Jira SoftWare\atlassian-jira\WEB-INF\lib下,与前面的安装目录对应,路径看下图,然后重启Jira
③. 重启后稍等几秒后,刷新Jira网页,会看到Jira正在启动并初始化...
④. 启动并初始化成功后,需要对Jira数据库进行配置,可选Jira的内置数据库,可以自己配置数据库,在配置数据库的时候我选了自己的,但发现版本过高时总是连接不上,于是选了mysql5.6版,这个库一般用不着。(如果想选自己的库,需要安装mysql5.6,具体安装步骤与安装包自行摸索),信息填写完后,点击“测试连接”,在上方出现如图中的连接测试成功,则点击下一步。
⑤. 设置Jira应用程序属性,记住这两个属性,做好不要改,在后面Jenkins中要使用,点击下一步
⑥. 记住这个页面的服务器ID,这里要输入许可证,我们没有许可证,需要去官网获取,获取时后面要使用这个服务器ID,要用邮箱注册
⑦. 注册成功会打开管理,点击“New Trial License”
⑧. 之后进入以下页面,按图操作,选择Jira Software - Jira Software(Data Center),生成许可证
⑨. 复制生成的许可证,填写到Jira中(此处获得的许可证其实是使用许可证,但上面添加的破解Jar包会改变使用日期)
⑩. 添加管理员账号,完成,等待Jira自动执行安装的最后一步
安装成功之后会让你选择语言(中国)、头像等,最终进入以下界面:
选择 “导入问题”,然后 “跳过” ,去往Jira主界面
四:Jira 创建项目与集成
①. 在主界面创建项目,选择“任务管理”,下一步。(我主要是用来自动发布bug任务给指定的人去修复)
②. 直接点“选择”进入下一阶段,填写项目名称与关键字,要记住这个关键字,后面Jenkins要用到. 然后提交即可。
③. 前面忘记查看许可证了,这里说一下,点击右上角的齿轮设置按钮-选择应用程序,可能让你重新登录,登录输入管理员密码即可,进入应用程序界面,可以看到许可证还有十几年才过期。
④. 点击“问题”,可以看到四种问题类型,分别为:Epic、任务、故事、子任务。 我是要创建BUG,那么我们来新增一个BUG类型,点击右边的“添加问题类型”
⑤. 新增类型成功后你还可以编辑它,给它一个图标。现在我们要进入“问题类型方案”模块,可以看到BUG这个类型并不在我创建的项目中,代表这个项目还无法使用它,点击编辑
以拖拽的形式将BUG类型拖到左边来,保存。
⑥. 让Jira可以访问Jenkins,点击应用程序链接,进行配置
特别申明:此时应该停下来先安装好Jenkins,可查看下方第五大项。
⑦. 应用程序链接创建成功后,再次编辑它,去配置 传出认证,开启,之后可以在列表上直接跳转到Jenkins。
五:Jenkins的安装与配置
①. 下载可兼容jdk1.8版本的jenkins.war,下载地址 https://get.jenkins.io/war-stable/ 这里有点坑,版本下低了,后面的jenkins上安装jira插件时会因为jdk版本原因导致失败,版本选高了又不符合项目的jdk设定,我尝试几个版本后选择的是 jenkins-2.346.1.war
②. 将下载好的jenkins.war放在自定义磁盘目录,别误删了
③. 代开cmd窗口,用java -jar 启动jenkins.war,例如:java -jar D:/jenkins.war 默认的端口为8080,如果要修改可以用压缩工具打开看看,具体在哪里修改端口我没试过。(cmd窗口关闭则jenkins关闭)jenkins在启动时会打印登录密码,并生成一个密码文件,应该是在 C:\Users\主机名\.jenkins\secrets\initialAdminPassword 这个里面
④. 浏览器访问localhost:8080/jenkins ,之后输入管理员密码,之后开始安装Jira插件,Manage Jenkins - Manage Plugins 进入插件管理,选择Available,如下图,要安装的插件有:
Jira Issue Updater
Jira Pipeline Steps
Jira Trigger
JiraTestResultReporter
Pipeline
SSH
(前4个插件可与Jira交互,第5个可在Jenkins中编写工作流,语法比较难搞,第6个在第三方调用Jenkins Api时需要使用,否则远程调用会报错)
⑤. 安装插件比较耗时,在安装完之后,要重启Jenkins,关闭Jenkins的cmd窗口,打开重新java -jar启动它,让它重新加载刚才安装的插件。 之后开始填写与Jira的交互配置,进入系统配置 Manage Jenkins - Configure System,按下面几个图配置,之后保存
⑥. 进入全局配置,设置Jenkins被第三方访问时的限制。
⑦. 在Jenkins上创建一个项目,我在此处选择了Pipeline工作流形式的项目
⑧. 创建项目完成后,编写项目的构建等配置,让其可以与Jira交互
脚本详情如下,%%中间的时参数,这些参数对应上面General创建的Choice Paramter、String Paramter等参数,命令中的http地址是Jira创建工单的API接口
pipeline {
agent any
stages {
stage('Build') {
steps {
bat script {"""curl -u %jire_user%:%jire_password% --header "Content-Type:application/json" -X POST --data "{\\"fields\\":{\\"project\\":{\\"key\\":\\"%project_key%\\"},\\"summary\\":\\"%summary%\\",\\"description\\":\\"%description%\\",\\"duedate\\":\\"%duedate%\\",\\"priority\\":{\\"name\\":\\"%priority%\\"},\\"assignee\\":{\\"name\\":\\"%assignee%\\"},\\"issuetype\\":{\\"name\\":\\"%issuetype%\\"}}}" http://%jira_url%/rest/api/2/issue/"""}
}
}
}
}
⑨. 创建完成后保存,我们可以运行一下
更改一下电脑的字符编码
每一个属性都对的上,成功了。 下面粘贴上Jenkins与Jira的各类API地址
Jira Api: JIRA 7.2.8 (atlassian.com)
Jenkins Api: Jenkins - API详解 - Anliven - 博客园 (cnblogs.com)
六:JAVA代码调用Jenkins,让Jenkins项目自动运行,然后执行Pipeline脚本,自动在Jira上创建BUG工单
工具类:
package com.hdlh.supplychain.controller;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.hdlh.common.utils.StringUtils;
/**
* Jenkins 构建任务(此处的构建可理解为执行的意思)
* @author LiuQingYuan
*
*/
public class JenkinsBuild {
private static final Logger log = LoggerFactory.getLogger(JenkinsBuild.class);
public static String username;
public static String password;
public static String url;
public static String token;
public static String jobName;
private static String buildCmd;
//private static String[] infoTypes = new String[] {"JSON","XML"};
public static String projectKey;
public static String issueType;
public static String jiraUrl;
public static String jiraUser;
public static String jiraPassword;
public static String defaultPriority;
public static String defaultAssignee;
public static void initJenkis() {
if(StringUtils.isEmpty(username)) {
try {
Properties properties = new Properties();
InputStream in = JenkinsBuild.class.getClassLoader().getResourceAsStream("com/hdlh/supplychain/controller/Jenkins.properties");
properties.load(in);
//Jenkis 地址
url = properties.getProperty("JENKINS_URL");
//Jenkis 任务名称
jobName = properties.getProperty("JENKINS_JOBNAME");
//Jenkis 任务调用Token
token = properties.getProperty("JENKINS_TOKEN");
//Jenkis 登录用户
username = properties.getProperty("JENKINS_USERNAME");
//Jenkis 登录密码
password = properties.getProperty("JENKINS_PASSWORD");
//Jenkins 要对接的Jira地址
jiraUrl = properties.getProperty("JIRA_URL");
//Jenkins 要对接的Jira登录用户
jiraUser = properties.getProperty("JIRA_USER");
//Jenkins 要对接的Jira登录密码
jiraPassword = properties.getProperty("JIRA_PASSWORD");
//Jenkins 要对接的Jira项目Key
projectKey = properties.getProperty("JIRA_PROJECT_KEY");
//Jenkis 要对接的Jira有哪些工单类型,默认取第一个工单类型
issueType = properties.getProperty("JIRA_ISSUE_TYPE");
//Jira 默认的工单风险级别 Highest/High/Medium/Low/Lowest
defaultPriority = properties.getProperty("JIRA_DEFAULT_PRIORITY");
//Jira 默认的工单处理人
defaultAssignee = properties.getProperty("JIRA_DEFAULT_ASSIGNEE");
buildCmd = "curl -X POST -v -u "+username+":"+password+" http://"+url+"/job/"+jobName;
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 无参构建
* @param Jenkins中的任务名称
*/
public static String jobBuildNoParam() {
log.info("execute a no param's build for "+jobName);
String NoParamBuildCmd = buildCmd + "/build?token="+token;
return ExecuteCmd(NoParamBuildCmd);
}
/**
* 有参构建
* @param 参数集
* 注意要点(踩过的坑)
* 一、需要在cmd中添加crumb,否则无法远程调用;但添加之后依旧不通,查看了很多文档最后发现还要添加一个Cookie,
* 如何获取Cookie与Crumb?多次测试后发现为-> curl -verbose -u user:password http://ip:port/crumbIssuer/api/json
* 网上找到的唯一例子中的cmd命令也是错的,这里的-verbose -i输出了请求头信息,其中包含了Cookie,如果不添加则获取不到
* 二、在获取到Cookie与Crumb后,将其添加到Cmd中,网上的版本大多不可信,方法中的命令经过多方测试为正确,切记传参时字符串不可
* 用单引号包裹,需要用双引号或不添加,如果参数中有空格时,必须使用双引号,否则Jenkins任务会调用成功,但是参数会丢失
* 三、请求方式POST,必须是大写,否则会失败
* 四、经多次测试发现,cmd命令中-d或--data或--data-urlencode后面只能传一个参数,若想传递多个,需要在cmd命令中写多个-d或--data或--data-urlencode
* @return
*/
public static String jobBuildWithParam(Map
Map
if(map.size() != 2) {
System.out.println("未找到Cookie与Crumb,请核实Jenkins是否启动成功");
return null;
}
log.info("execute a with param's build for "+jobName);
String withParamBuildCmd = buildCmd + "/buildWithParameters?token="+token + " -H \"Jenkins-Crumb:"+map.get("crumb")+"\" --cookie \""+map.get("cookie")+"\" ";
StringBuffer sb = new StringBuffer();
for(String key:paramMap.keySet()) {
String signParamStr = "--data-urlencode \""+key+"="+paramMap.get(key)+"\" ";
sb.append(signParamStr);
}
withParamBuildCmd += sb.toString();
return ExecuteCmd(withParamBuildCmd);
}
/**
* 获取最后一次构建的ID
* @return
*/
public static String getLastBuildId() {
log.info("get last build id for "+jobName);
String getLastBuidIdCmd = buildCmd + "/lastBuild/buildNumber";
return ExecuteCmd(getLastBuidIdCmd);
}
/**
* 获取最后一次构建的结果
* @param buildId
* @return
*/
public static String getBuildResult(int buildId,String type) {
log.info("get build result for "+jobName+", and build id is "+buildId);
String getLastBuildResultCmd = buildCmd;
if("XML".equals(type)) {
getLastBuildResultCmd += "/"+buildId+"/api/xml";
}else {
getLastBuildResultCmd += "/"+buildId+"/api/json?pretty=true";
}
return ExecuteCmd(getLastBuildResultCmd);
}
/**
* 获取Cookie与Crumb
* @return
*/
public static Map
log.info("get cookie and crumb for "+jobName+"'s parametric construction");
String getCookieAndCrumbCmd = "curl -verbose -i -u "+username+":"+password+" http://"+url+"/crumbIssuer/api/json";
return ExecuteCmdForCookieAndCrumb(getCookieAndCrumbCmd);
}
/**
* 执行CMD命令
* @param command
*/
public static String ExecuteCmd(String command) {
String result = "";
BufferedReader br = null;
try {
System.out.println("command:"+command);
Process process = Runtime.getRuntime().exec("cmd /c "+command);
br = new BufferedReader(new InputStreamReader(process.getInputStream(),Charset.forName("GBK")));
String line = null;
StringBuilder sb = new StringBuilder();
while ((line = br.readLine()) != null) {
sb.append(line + "\n");
}
System.out.println("command result:"+sb.toString());
result = sb.toString().trim();
}catch(IOException e) {
e.printStackTrace();
}finally {
if (br != null){
try {
br.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
return result;
}
/**
* 执行获取Cookie与Crumb的CMD命令
* @param command
*/
public static Map
Map
BufferedReader br = null;
try {
System.out.println("command:"+command);
Process process = Runtime.getRuntime().exec("cmd /c "+command);
br = new BufferedReader(new InputStreamReader(process.getInputStream(),Charset.forName("GBK")));
String line = null;
while ((line = br.readLine()) != null) {
if(line.toLowerCase().contains("cookie")) {
String cookie = line.substring(line.indexOf(":")+1,line.indexOf(";")).trim();
returnMap.put("cookie", cookie);
}
if(line.contains("\"crumb\"")) {
line = line.substring(line.indexOf("\"crumb\""));
String crumb = line.substring(line.indexOf(":")+1,line.indexOf(",")).replaceAll("\"", "");
returnMap.put("crumb", crumb);
}
}
System.out.println("command result:{\"cookie\":\""+returnMap.get("cookie")+"\",\"crumb\":\""+returnMap.get("crumb")+"\"}");
}catch(IOException e) {
e.printStackTrace();
}finally {
if (br != null){
try {
br.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
return returnMap;
}
public static void main(String[] args) {
/*
* String buildIdStr = JenkinsBuild.getLastBuildId();
* if(StringUtils.isNotEmpty(buildIdStr)) {
* JenkinsBuild.getBuildResult(Integer.parseInt(buildIdStr),infoTypes[0]); }
*/
//JenkinsBuild.getCookieAndCrumb();
/*
* Map
* paramMap.put("jira_url", "localhost:8088"); paramMap.put("jira_user",
* username); paramMap.put("jira_password", password);
* paramMap.put("project_key", projectKey); paramMap.put("issuetype", "BUG");
* //paramMap.put("component", "file scan"); paramMap.put("summary", "测试");
* paramMap.put("description", "这是一条测试数据"); paramMap.put("priority", "Lowest");
* paramMap.put("duedate", "2022-10-21"); paramMap.put("assignee", "hcs");
*
* JenkinsBuild.jobBuildWithParam(paramMap);
*/
JenkinsBuild.initJenkis();
}
}
Jira工单入参说明类:
package com.hdlh.supplychain.controller;
/**
* 调用Jenkins构建build时的入参 (改实体在项目中没有使用,提供给调用Jenkins参数编写时参照,也可在搭建的Jenkins网页上查看)
* @author LiuQingYuan
*
*/
public class JenkinsBuildParam {
/** Jira环境的地址,默认设置为localhost:8088,请根据实际情况调整 */
private String jira_url;
/** Jire环境的登录账号,默认设置为root,请根据实际情况调整 */
private String jira_user;
/** Jire环境的登录密码,默认设置为Hdlh@2605,请根据实际情况调整 */
private String jira_password;
/** 问题所属项目关键词-项目的KEY,默认设置为HDLH,请根据实际情况调整 */
private String project_key;
/** 问题类型 - 默认定义为BUG,填写时请确认Jira中的该项目是否有这个问题类型 */
private String issuetype;
/** 问题在哪个模块出现的 */
//private String component; 当前版本好像不支持这个属性
/** 问题概述/总结 */
private String summary;
/** 问题详情描述 */
private String description;
/** 问题风险等级 - 可取值 Highest/High/Medium/Low/Lowest */
private String priority;
/** 问题截止日期 格式 YYYY-MM-dd */
private String duedate;
/** 问题指派给谁 - 填写时请确认Jira中是否有改人员账号,此处填写的是账号,不是全名 */
private String assignee;
public String getJira_url() {
return jira_url;
}
public void setJira_url(String jira_url) {
this.jira_url = jira_url;
}
public String getJira_user() {
return jira_user;
}
public void setJira_user(String jira_user) {
this.jira_user = jira_user;
}
public String getJira_password() {
return jira_password;
}
public void setJira_password(String jira_password) {
this.jira_password = jira_password;
}
public String getProject_key() {
return project_key;
}
public void setProject_key(String project_key) {
this.project_key = project_key;
}
public String getIssuetype() {
return issuetype;
}
public void setIssuetype(String issuetype) {
this.issuetype = issuetype;
}
/*
* public String getComponent() { return component; } public void
* setComponent(String component) { this.component = component; }
*/
public String getSummary() {
return summary;
}
public void setSummary(String summary) {
this.summary = summary;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getPriority() {
return priority;
}
public void setPriority(String priority) {
this.priority = priority;
}
public String getDuedate() {
return duedate;
}
public void setDuedate(String duedate) {
this.duedate = duedate;
}
public String getAssignee() {
return assignee;
}
public void setAssignee(String assignee) {
this.assignee = assignee;
}
}
jenkins.properties文件中的属性
JENKINS_URL=localhost:8080
JENKINS_JOBNAME=Jira-HDLH-Pipeline
JENKINS_TOKEN=Hdlh@2605
JENKINS_USERNAME=admin
JENKINS_PASSWORD=你自己的Jenkins登录密码
JIRA_URL=localhost:8088
JIRA_USER=root
JIRA_PASSWORD=Hdlh@2605
JIRA_PROJECT_KEY=HDLH
JIRA_ISSUE_TYPE=BUG
JIRA_DEFAULT_PRIORITY=Medium
JIRA_DEFAULT_ASSIGNEE=hcs 可以在Jira中新建用户,此处是用户账号