第一次做Jenkins插件开发,遂将笔记公开分享
插件名称: gettingCase
插件功能: 获取RallyDev上的某一个Test Case信息
0.配置.m2/settings.xml
请查阅本文最后的参考资料
1.Maven创建Jenkins插件项目
mvn -U org.jenkins-ci.tools:maven-hpi-plugin:create
第一次执行会比较慢,因为需要下载很多Maven插件.
这个创建项目的过程有2步互动:
第一步需要开发者输入Maven项目的groupId
Enter the groupId of your plugin [org.jenkins-ci.plugins]:
com.technicolor.qcs
第二步需要开发者输入Maven项目的artifactId
Enter the artifactId of your plugin (normally without '-plugin' suffix):
gettingCase
2.基于Hello World插件项目,修改以实现自己插件功能
2.1POM
修改pom.xml文件,增加REST访问RallyDev的工具包
4.0.0
org.jenkins-ci.plugins
plugin
1.509.3
com.technicolor.qcs
gettingCase
1.0-SNAPSHOT
hpi
Gets Rally Test Cases
feuyeux
eric han
[email protected]
com.rallydev.rest
rally-rest-api
2.0.4
org.apache.httpcomponents
httpcore
4.2.1
org.apache.httpcomponents
httpclient
4.2.1
org.apache.httpcomponents
httpclient-cache
4.2.1
org.apache.httpcomponents
httpmime
4.2.1
org.apache.httpcomponents
fluent-hc
4.2.1
org.apache.commons
commons-lang3
3.1
commons-logging
commons-logging
1.1.1
commons-codec
commons-codec
1.6
com.google.code.gson
gson
2.1
repo.jenkins-ci.org
http://repo.jenkins-ci.org/public/
repo.jenkins-ci.org
http://repo.jenkins-ci.org/public/
2.2 编写全局配置页面
/home/hanl/j-ci/gettingCase/src/main/resources/com/technicolor/qcs/gettingCase/GetCasesBuilder/global.jelly
2.3 编写JOB配置页面
1 app Hudson应用程序对象
${app.displayName}. //应用的Display名称
2 it 当前UI所属的模型对象
${it.name} 对应于builder的getName()方法
3 h 一个全局的工具类,提供静态工具方法
/home/hanl/j-ci/gettingCase/src/main/resources/com/technicolor/qcs/gettingCase/GetCasesBuilder/config.jelly
2.4 编写扩展点方法
一次构建过程通常包括:
SCM checkout - check out出源码
Pre-build - 预编译
Build wrapper -准备构建的环境,设置环境变量等
Builder runs - 执行构建,比如调用calling Ant, Make
Recording - 记录输出,如测试结果
Notification - 通知成员
/home/hanl/j-ci/gettingCase/src/main/java/com/technicolor/qcs/gettingCase/GetCasesBuilder.java
package com.technicolor.qcs.gettingCase;
import hudson.Launcher;
import hudson.Extension;
import hudson.util.FormValidation;
import hudson.model.AbstractBuild;
import hudson.model.BuildListener;
import hudson.model.AbstractProject;
import hudson.tasks.Builder;
import hudson.tasks.BuildStepDescriptor;
import net.sf.json.JSONObject;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.QueryParameter;
import javax.servlet.ServletException;
import java.io.IOException;
import java.io.PrintStream;
/**
* author:feuyeux
*/
public class GetCasesBuilder extends Builder {
public static final String RALLY_URL = "https://rally1.rallydev.com";
private final String testCaseId;
@DataBoundConstructor
public GetCasesBuilder(String testCaseId) {
this.testCaseId = testCaseId;
}
public String getTestCaseId() {
return testCaseId;
}
@Override
public boolean perform(AbstractBuild build, Launcher launcher, BuildListener listener) {
PrintStream out = listener.getLogger();
final String userName = getDescriptor().getUserName();
final String password = getDescriptor().getPassword();
final String proxyURL = getDescriptor().getProxyURL();
final String proxyUser = getDescriptor().getProxyUser();
final String proxyPassword = getDescriptor().getProxyPassword();
out.println("RallyDev User Name =" + userName);
out.println("HTTP Proxy=" + proxyUser + "@" + proxyURL);
out.println("RallyDev Test Case =" + getTestCaseId()+"\n");
RallyClient rallyClient = null;
try {
rallyClient = new RallyClient(RALLY_URL, userName, password, proxyURL, proxyUser, proxyPassword);
String caseInfo = rallyClient.getTestCases(testCaseId);
out.println(caseInfo);
} catch (Exception e) {
out.println(e.getMessage());
} finally {
try {
rallyClient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return true;
}
@Override
public DescriptorImpl getDescriptor() {
return (DescriptorImpl) super.getDescriptor();
}
@Extension
public static final class DescriptorImpl extends BuildStepDescriptor {
private String userName;
private String password;
private String proxyURL;
private String proxyUser;
private String proxyPassword;
public FormValidation doCheckName(@QueryParameter String value)
throws IOException, ServletException {
if (value.length() == 0)
return FormValidation.error("Please set a testCaseId");
return FormValidation.ok();
}
public boolean isApplicable(Class extends AbstractProject> aClass) {
return true;
}
public String getDisplayName() {
return "Getting Test cases from Rally";
}
@Override
public boolean configure(StaplerRequest req, JSONObject formData) throws FormException {
userName = formData.getString("userName");
password = formData.getString("password");
proxyURL = formData.getString("proxyURL");
proxyUser = formData.getString("proxyUser");
proxyPassword = formData.getString("proxyPassword");
save();
return super.configure(req, formData);
}
public String getUserName() {
return userName;
}
public String getPassword() {
return password;
}
public String getProxyURL() {
return proxyURL;
}
public String getProxyUser() {
return proxyUser;
}
public String getProxyPassword() {
return proxyPassword;
}
}
}
2.5 编写RallyDev连接和访问方法
/home/hanl/j-ci/gettingCase/src/main/java/com/technicolor/qcs/gettingCase/RallyClient.java
package com.technicolor.qcs.gettingCase;
import com.google.gson.JsonObject;
import com.rallydev.rest.RallyRestApi;
import com.rallydev.rest.request.GetRequest;
import com.rallydev.rest.response.GetResponse;
import com.rallydev.rest.response.Response;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
/**
* author:feuyeux
*/
public class RallyClient {
/*https://github.com/RallyTools/RallyRestToolkitForJavahttps://github.com/RallyTools/RallyRestToolkitForJava */
private final RallyRestApi restApi;
public RallyClient(String rally_url, String userName, String password, String proxyURL, String proxyUser, String proxyPassword) throws URISyntaxException {
restApi = new RallyRestApi(new URI(rally_url), userName, password);
restApi.setProxy(new URI(proxyURL), proxyUser, proxyPassword);
}
public void close() throws IOException {
restApi.close();
}
public String getTestCases(String testCaseId) {
/*https://rally1.rallydev.com/slm/doc/webservice/*/
StringBuilder result = new StringBuilder();
String version = restApi.getWsapiVersion();
result.append("RallyDev Rest Version=").append(version).append("\n");
final String query = "/testcase/"+testCaseId;
GetRequest queryRequest = new GetRequest(query);
GetResponse casesResponse = null;
try {
casesResponse = restApi.get(queryRequest);
} catch (IOException e) {
e.printStackTrace();
}
printWarningsOrErrors(casesResponse, result);
JsonObject caseJsonObject = casesResponse.getObject();
result.append("\n").append("Test Case Name: ").append(caseJsonObject.get("Name").getAsString());
result.append("\n").append("Test Case Type: ").append(caseJsonObject.get("Type").getAsString());
result.append("\n").append("Test Case URL: ").append(caseJsonObject.get("_ref").getAsString());
result.append("\n").append("Test Case Creation Time: ").append(caseJsonObject.get("CreationDate").getAsString());
result.append("\n").append("Test Case LastUpdate Time: ").append(caseJsonObject.get("LastUpdateDate").getAsString());
result.append("\n").append("Test Case's Project: ").append( caseJsonObject.get("Project").getAsJsonObject().get("_refObjectName") .getAsString());
result.append("\n").append("Test Case's Workspace: ").append( caseJsonObject.get("Workspace").getAsJsonObject().get("_refObjectName") .getAsString());
return result.toString();
}
private void printWarningsOrErrors(Response response, StringBuilder result) {
if (response.wasSuccessful()) {
result.append("\nSuccess.");
String[] warningList;
warningList = response.getWarnings();
for (int i = 0; i < warningList.length; i++) {
result.append("\twarning:\n" + warningList[i]);
}
} else {
String[] errorList;
errorList = response.getErrors();
if (errorList.length > 0) {
result.append("\nError.");
}
for (int i = 0; i < errorList.length; i++) {
result.append("\terror:\n" + errorList[i]);
}
}
}
}
3.调试Plugin程序
在终端/控制台,首先执行Maven变量配置命令
set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,address=8000,suspend=n
cd到插件项目目录,执行如下命令
hanl@hanl-ubuntu1204:~/j-ci/gettingCase$ mvn clean
hanl@hanl-ubuntu1204:~/j-ci/gettingCase$ mvnDebug hpi:run
Maven将对8000端口执行监听,以便在IDE中进行断点调试
Preparing to Execute Maven in Debug Mode
Listening for transport dt_socket at address: 8000
进入IDE(本例使用IntelliJ),选择Run菜单的Debug,添加一个8000端口的远程服务器
添加断点,点击左下角运行调试按钮
此时,终端将执行Maven构建,并启动Jetty服务器.
hanl@hanl-ubuntu1204:~/j-ci/gettingCase$ mvnDebug hpi:run
Preparing to Execute Maven in Debug Mode
Listening for transport dt_socket at address: 8000
[INFO] Scanning for projects...
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] Building gettingCase 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO]
[INFO] >>> maven-hpi-plugin:1.95:run (default-cli) @ gettingCase >>>
[INFO]
[INFO] --- maven-hpi-plugin:1.95:validate (default-validate) @ gettingCase ---
[INFO]
[INFO] --- maven-enforcer-plugin:1.0.1:enforce (enforce-maven) @ gettingCase ---
[INFO]
[INFO] --- maven-enforcer-plugin:1.0.1:display-info (display-info) @ gettingCase ---
[INFO] Maven Version: 3.0.4
[INFO] JDK Version: 1.7.0_25 normalized as: 1.7.0-25
[INFO] OS Info: Arch: amd64 Family: unix Name: linux Version: 3.2.0-54-generic
[INFO]
[INFO] --- maven-localizer-plugin:1.14:generate (default) @ gettingCase ---
[INFO]
[INFO] --- maven-resources-plugin:2.5:resources (default-resources) @ gettingCase ---
[debug] execute contextualize
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] Copying 5 resources
[INFO]
[INFO] --- maven-compiler-plugin:2.5:compile (default-compile) @ gettingCase ---
[INFO] Nothing to compile - all classes are up to date
[INFO]
[INFO] <<< maven-hpi-plugin:1.95:run (default-cli) @ gettingCase <<<
[INFO]
[INFO] --- maven-hpi-plugin:1.95:run (default-cli) @ gettingCase ---
[INFO] Generating ./work/plugins/gettingCase.hpl
[INFO] Copying dependency Jenkins plugin /home/hanl/.m2/repository/org/jenkins-ci/plugins/mailer/1.4/mailer-1.4.jar
[INFO] Copying dependency Jenkins plugin /home/hanl/.m2/repository/org/jenkins-ci/plugins/ant/1.1/ant-1.1.jar
[INFO] Copying dependency Jenkins plugin /home/hanl/.m2/repository/org/jenkins-ci/main/maven-plugin/1.509.3/maven-plugin-1.509.3.jar
[INFO] Copying dependency Jenkins plugin /home/hanl/.m2/repository/org/jenkins-ci/plugins/javadoc/1.0/javadoc-1.0.jar
[INFO] Copying dependency Jenkins plugin /home/hanl/.m2/repository/org/jenkins-ci/plugins/subversion/1.26/subversion-1.26.jar
[INFO] Copying dependency Jenkins plugin /home/hanl/.m2/repository/org/jenkins-ci/main/ui-samples-plugin/1.509.3/ui-samples-plugin-1.509.3.jar
[INFO] Configuring Jetty for project: gettingCase
2013-10-10 18:10:55.444::INFO: Logging to STDERR via org.mortbay.log.StdErrLog
[INFO] Context path = /jenkins
[INFO] Tmp directory = /home/hanl/j-ci/gettingCase/target/work
[INFO] Web defaults = jetty default
[INFO] Starting jetty 6.1.1 ...
2013-10-10 18:10:55.587::INFO: jetty-6.1.1
Jenkins home directory: /home/hanl/j-ci/gettingCase/./work found at: System.getProperty("HUDSON_HOME")
2013-10-10 18:10:59.572::INFO: Started SelectChannelConnector @ 0.0.0.0:8080
[INFO] Started Jetty Server
[INFO] Console reloading is ENABLED. Hit ENTER on the console to restart the context.
Oct 10, 2013 6:10:59 PM jenkins.InitReactorRunner$1 onAttained
INFO: Started initialization
Oct 10, 2013 6:11:01 PM jenkins.InitReactorRunner$1 onAttained
INFO: Listed all plugins
Oct 10, 2013 6:11:01 PM jenkins.InitReactorRunner$1 onAttained
INFO: Prepared all plugins
Oct 10, 2013 6:11:01 PM jenkins.InitReactorRunner$1 onAttained
INFO: Started all plugins
Oct 10, 2013 6:11:01 PM jenkins.InitReactorRunner$1 onAttained
INFO: Augmented all extensions
Oct 10, 2013 6:11:04 PM jenkins.InitReactorRunner$1 onAttained
INFO: Loaded all jobs
Oct 10, 2013 6:11:05 PM org.jenkinsci.main.modules.sshd.SSHD start
INFO: Started SSHD at port 54676
Oct 10, 2013 6:11:05 PM jenkins.InitReactorRunner$1 onAttained
INFO: Completed initialization
Oct 10, 2013 6:11:05 PM hudson.TcpSlaveAgentListener
INFO: JNLP slave agent listener started on TCP port 47342
Oct 10, 2013 6:11:05 PM hudson.WebAppMain$2 run
INFO: Jenkins is fully up and running
4.测试
在浏览器中录入Jenkins地址,创建Job并按照上述的配置环节,完成配置.
执行Build Job
此时,断点应其作用,可以在IDE中进行调试和监控.
当完成调试后,进入Jenkins构建结果页面,观察构建结果是否符合预期.
到此,获取并显示RallyDev中Test Case的Jenkins插件开发完毕.
IntelliJ IDE 插件:
https://wiki.jenkins-ci.org/display/JENKINS/IntelliJ+IDEA+plugin+for+Stapler
Stapler plugin for IntelliJ IDEA
参考资料
https://wiki.jenkins-ci.org/display/JENKINS/Plugin+tutorial
https://github.com/jenkinsci/hello-world-plugin
https://jenkins-ci.org/maven-site/jenkins-core/jelly-taglib-ref.html
ui-samples: http://localhost:8080/jenkins/ui-samples/
扩展点:https://wiki.jenkins-ci.org/display/JENKINS/Extension+points
${inst.url}