第一次做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的工具包

Xml代码   收藏代码
  1. <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
  2.          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">  
  3.     <modelVersion>4.0.0</modelVersion>  
  4.     <parent>  
  5.         <groupId>org.jenkins-ci.plugins</groupId>  
  6.         <artifactId>plugin</artifactId>  
  7.         <version>1.509.3</version>  
  8.         <!-- which version of Jenkins is this plugin built against? -->  
  9.     </parent>  
  10.   
  11.     <groupId>com.technicolor.qcs</groupId>  
  12.     <artifactId>gettingCase</artifactId>  
  13.     <version>1.0-SNAPSHOT</version>  
  14.     <packaging>hpi</packaging>  
  15.     <description>Gets Rally Test Cases</description>  
  16.   
  17.     <developers>  
  18.         <developer>  
  19.             <id>feuyeux</id>  
  20.             <name>eric han</name>  
  21.             <email>feuyeux@gmail.com</email>  
  22.         </developer>  
  23.     </developers>  
  24.     <dependencies>  
  25.         <dependency>  
  26.             <groupId>com.rallydev.rest</groupId>  
  27.             <artifactId>rally-rest-api</artifactId>  
  28.             <version>2.0.4</version>  
  29.         </dependency>  
  30.         <dependency>  
  31.             <groupId>org.apache.httpcomponents</groupId>  
  32.             <artifactId>httpcore</artifactId>  
  33.             <version>4.2.1</version>  
  34.         </dependency>  
  35.         <dependency>  
  36.             <groupId>org.apache.httpcomponents</groupId>  
  37.             <artifactId>httpclient</artifactId>  
  38.             <version>4.2.1</version>  
  39.         </dependency>  
  40.         <dependency>  
  41.             <groupId>org.apache.httpcomponents</groupId>  
  42.             <artifactId>httpclient-cache</artifactId>  
  43.             <version>4.2.1</version>  
  44.         </dependency>  
  45.         <dependency>  
  46.             <groupId>org.apache.httpcomponents</groupId>  
  47.             <artifactId>httpmime</artifactId>  
  48.             <version>4.2.1</version>  
  49.         </dependency>  
  50.         <dependency>  
  51.             <groupId>org.apache.httpcomponents</groupId>  
  52.             <artifactId>fluent-hc</artifactId>  
  53.             <version>4.2.1</version>  
  54.         </dependency>  
  55.         <dependency>  
  56.             <groupId>org.apache.commons</groupId>  
  57.             <artifactId>commons-lang3</artifactId>  
  58.             <version>3.1</version>  
  59.         </dependency>  
  60.         <dependency>  
  61.             <groupId>commons-logging</groupId>  
  62.             <artifactId>commons-logging</artifactId>  
  63.             <version>1.1.1</version>  
  64.         </dependency>  
  65.         <dependency>  
  66.             <groupId>commons-codec</groupId>  
  67.             <artifactId>commons-codec</artifactId>  
  68.             <version>1.6</version>  
  69.         </dependency>  
  70.         <dependency>  
  71.             <groupId>com.google.code.gson</groupId>  
  72.             <artifactId>gson</artifactId>  
  73.             <version>2.1</version>  
  74.         </dependency>  
  75.     </dependencies>  
  76.   
  77.     <repositories>  
  78.         <repository>  
  79.             <id>repo.jenkins-ci.org</id>  
  80.             <url>http://repo.jenkins-ci.org/public/</url>  
  81.         </repository>  
  82.     </repositories>  
  83.   
  84.     <pluginRepositories>  
  85.         <pluginRepository>  
  86.             <id>repo.jenkins-ci.org</id>  
  87.             <url>http://repo.jenkins-ci.org/public/</url>  
  88.         </pluginRepository>  
  89.     </pluginRepositories>  
  90. </project>  

 2.2 编写全局配置页面

/home/hanl/j-ci/gettingCase/src/main/resources/com/technicolor/qcs/gettingCase/GetCasesBuilder/global.jelly

 

Java代码   收藏代码
  1. <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">  
  2.   <f:section title="Getting Test Cases Builder">  
  3.     <f:entry title="RallyDev User Name" field="userName">  
  4.       <f:textbox />  
  5.     </f:entry>  
  6.     <f:entry title="RallyDev Password" field="password">  
  7.       <f:password/>  
  8.     </f:entry>  
  9.   
  10.     <f:entry title="HTTP Proxy URL" field="proxyURL">  
  11.       <f:textbox />  
  12.     </f:entry>  
  13.     <f:entry title="RallyDev Proxy User Name" field="proxyUser">  
  14.       <f:textbox />  
  15.     </f:entry>  
  16.     <f:entry title="RallyDev Proxy Password" field="proxyPassword">  
  17.       <f:password />  
  18.     </f:entry>  
  19.   </f:section>  
  20. </j:jelly>  
 

 2.3 编写JOB配置页面

Java代码   收藏代码
  1. 1  app  Hudson应用程序对象  
  2. ${app.displayName}. //应用的Display名称  
  3.   
  4. 2  it   当前UI所属的模型对象  
  5. ${it.name} 对应于builder的getName()方法  
  6.   
  7. 3  h   一个全局的工具类,提供静态工具方法     

 

/home/hanl/j-ci/gettingCase/src/main/resources/com/technicolor/qcs/gettingCase/GetCasesBuilder/config.jelly

Java代码   收藏代码
  1. <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">  
  2.   <f:entry title="Test Case No." field="testCaseId">  
  3.     <f:textbox />  
  4.   </f:entry>  
  5. </j:jelly>  

 

2.4 编写扩展点方法

 

Java代码   收藏代码
  1. 一次构建过程通常包括:  
  2. SCM checkout - check out出源码  
  3. Pre-build    - 预编译  
  4. Build wrapper  -准备构建的环境,设置环境变量等  
  5. Builder runs   - 执行构建,比如调用calling Ant, Make  
  6. Recording    - 记录输出,如测试结果  
  7. Notification    - 通知成员  

 

/home/hanl/j-ci/gettingCase/src/main/java/com/technicolor/qcs/gettingCase/GetCasesBuilder.java

Java代码   收藏代码
  1. package com.technicolor.qcs.gettingCase;  
  2.   
  3. import hudson.Launcher;  
  4. import hudson.Extension;  
  5. import hudson.util.FormValidation;  
  6. import hudson.model.AbstractBuild;  
  7. import hudson.model.BuildListener;  
  8. import hudson.model.AbstractProject;  
  9. import hudson.tasks.Builder;  
  10. import hudson.tasks.BuildStepDescriptor;  
  11. import net.sf.json.JSONObject;  
  12. import org.kohsuke.stapler.DataBoundConstructor;  
  13. import org.kohsuke.stapler.StaplerRequest;  
  14. import org.kohsuke.stapler.QueryParameter;  
  15.   
  16. import javax.servlet.ServletException;  
  17. import java.io.IOException;  
  18. import java.io.PrintStream;  
  19.   
  20. /** 
  21.  * author:feuyeux 
  22.  */  
  23. public class GetCasesBuilder extends Builder {  
  24.     public static final String RALLY_URL = "https://rally1.rallydev.com";  
  25.     private final String testCaseId;  
  26.   
  27.     @DataBoundConstructor  
  28.     public GetCasesBuilder(String testCaseId) {  
  29.         this.testCaseId = testCaseId;  
  30.     }  
  31.   
  32.     public String getTestCaseId() {  
  33.         return testCaseId;  
  34.     }  
  35.   
  36.     @Override  
  37.     public boolean perform(AbstractBuild build, Launcher launcher, BuildListener listener) {  
  38.         PrintStream out = listener.getLogger();  
  39.         final String userName = getDescriptor().getUserName();  
  40.         final String password = getDescriptor().getPassword();  
  41.         final String proxyURL = getDescriptor().getProxyURL();  
  42.         final String proxyUser = getDescriptor().getProxyUser();  
  43.         final String proxyPassword = getDescriptor().getProxyPassword();  
  44.   
  45.         out.println("RallyDev User Name =" + userName);  
  46.         out.println("HTTP Proxy=" + proxyUser + "@" + proxyURL);  
  47.         out.println("RallyDev Test Case =" + getTestCaseId()+"\n");  
  48.         RallyClient rallyClient = null;  
  49.         try {  
  50.             rallyClient = new RallyClient(RALLY_URL, userName, password, proxyURL, proxyUser, proxyPassword);  
  51.             String caseInfo = rallyClient.getTestCases(testCaseId);  
  52.             out.println(caseInfo);  
  53.         } catch (Exception e) {  
  54.             out.println(e.getMessage());  
  55.         } finally {  
  56.             try {  
  57.                 rallyClient.close();  
  58.             } catch (IOException e) {  
  59.                 e.printStackTrace();  
  60.             }  
  61.         }  
  62.         return true;  
  63.     }  
  64.   
  65.     @Override  
  66.     public DescriptorImpl getDescriptor() {  
  67.         return (DescriptorImpl) super.getDescriptor();  
  68.     }  
  69.   
  70.     @Extension  
  71.     public static final class DescriptorImpl extends BuildStepDescriptor<Builder> {  
  72.         private String userName;  
  73.         private String password;  
  74.         private String proxyURL;  
  75.         private String proxyUser;  
  76.         private String proxyPassword;  
  77.   
  78.         public FormValidation doCheckName(@QueryParameter String value)  
  79.                 throws IOException, ServletException {  
  80.             if (value.length() == 0)  
  81.                 return FormValidation.error("Please set a testCaseId");  
  82.             return FormValidation.ok();  
  83.         }  
  84.   
  85.         public boolean isApplicable(Class<? extends AbstractProject> aClass) {  
  86.             return true;  
  87.         }  
  88.   
  89.         public String getDisplayName() {  
  90.             return "Getting Test cases from Rally";  
  91.         }  
  92.   
  93.         @Override  
  94.         public boolean configure(StaplerRequest req, JSONObject formData) throws FormException {  
  95.             userName = formData.getString("userName");  
  96.             password = formData.getString("password");  
  97.             proxyURL = formData.getString("proxyURL");  
  98.             proxyUser = formData.getString("proxyUser");  
  99.             proxyPassword = formData.getString("proxyPassword");  
  100.             save();  
  101.             return super.configure(req, formData);  
  102.         }  
  103.   
  104.         public String getUserName() {  
  105.             return userName;  
  106.         }  
  107.   
  108.         public String getPassword() {  
  109.             return password;  
  110.         }  
  111.   
  112.         public String getProxyURL() {  
  113.             return proxyURL;  
  114.         }  
  115.   
  116.         public String getProxyUser() {  
  117.             return proxyUser;  
  118.         }  
  119.   
  120.         public String getProxyPassword() {  
  121.             return proxyPassword;  
  122.         }  
  123.     }  
  124. }  

 2.5 编写RallyDev连接和访问方法

/home/hanl/j-ci/gettingCase/src/main/java/com/technicolor/qcs/gettingCase/RallyClient.java

Java代码   收藏代码
  1. package com.technicolor.qcs.gettingCase;  
  2.   
  3. import com.google.gson.JsonObject;  
  4. import com.rallydev.rest.RallyRestApi;  
  5. import com.rallydev.rest.request.GetRequest;  
  6. import com.rallydev.rest.response.GetResponse;  
  7. import com.rallydev.rest.response.Response;  
  8.   
  9. import java.io.IOException;  
  10. import java.net.URI;  
  11. import java.net.URISyntaxException;  
  12.   
  13. /** 
  14.  * author:feuyeux 
  15.  */  
  16. public class RallyClient {  
  17.     /*https://github.com/RallyTools/RallyRestToolkitForJavahttps://github.com/RallyTools/RallyRestToolkitForJava */  
  18.     private final RallyRestApi restApi;  
  19.   
  20.     public RallyClient(String rally_url, String userName, String password, String proxyURL, String proxyUser, String proxyPassword) throws URISyntaxException {  
  21.         restApi = new RallyRestApi(new URI(rally_url), userName, password);  
  22.         restApi.setProxy(new URI(proxyURL), proxyUser, proxyPassword);  
  23.     }  
  24.   
  25.     public void close() throws IOException {  
  26.         restApi.close();  
  27.     }  
  28.   
  29.     public String getTestCases(String testCaseId) {  
  30.         /*https://rally1.rallydev.com/slm/doc/webservice/*/  
  31.         StringBuilder result = new StringBuilder();  
  32.   
  33.         String version = restApi.getWsapiVersion();  
  34.         result.append("RallyDev Rest Version=").append(version).append("\n");  
  35.   
  36.         final String query = "/testcase/"+testCaseId;  
  37.         GetRequest queryRequest = new GetRequest(query);  
  38.         GetResponse casesResponse = null;  
  39.         try {  
  40.             casesResponse = restApi.get(queryRequest);  
  41.         } catch (IOException e) {  
  42.             e.printStackTrace();  
  43.         }  
  44.         printWarningsOrErrors(casesResponse, result);  
  45.         JsonObject caseJsonObject = casesResponse.getObject();  
  46.   
  47.         result.append("\n").append("Test Case Name: ").append(caseJsonObject.get("Name").getAsString());  
  48.         result.append("\n").append("Test Case Type: ").append(caseJsonObject.get("Type").getAsString());  
  49.         result.append("\n").append("Test Case URL: ").append(caseJsonObject.get("_ref").getAsString());  
  50.         result.append("\n").append("Test Case Creation Time: ").append(caseJsonObject.get("CreationDate").getAsString());  
  51.         result.append("\n").append("Test Case LastUpdate Time: ").append(caseJsonObject.get("LastUpdateDate").getAsString());  
  52.         result.append("\n").append("Test Case's Project: ").append( caseJsonObject.get("Project").getAsJsonObject().get("_refObjectName")    .getAsString());  
  53.         result.append("\n").append("Test Case's Workspace: ").append( caseJsonObject.get("Workspace").getAsJsonObject().get("_refObjectName")    .getAsString());  
  54.   
  55.         return result.toString();  
  56.     }  
  57.   
  58.     private void printWarningsOrErrors(Response response, StringBuilder result) {  
  59.         if (response.wasSuccessful()) {  
  60.             result.append("\nSuccess.");  
  61.             String[] warningList;  
  62.             warningList = response.getWarnings();  
  63.             for (int i = 0; i < warningList.length; i++) {  
  64.                 result.append("\twarning:\n" + warningList[i]);  
  65.             }  
  66.         } else {  
  67.             String[] errorList;  
  68.             errorList = response.getErrors();  
  69.             if (errorList.length > 0) {  
  70.                 result.append("\nError.");  
  71.   
  72.             }  
  73.             for (int i = 0; i < errorList.length; i++) {  
  74.                 result.append("\terror:\n" + errorList[i]);  
  75.             }  
  76.         }  
  77.     }  
  78. }  

 3.调试Plugin程序

在终端/控制台,首先执行Maven变量配置命令

Python代码   收藏代码
  1. set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,address=8000,suspend=n  

cd到插件项目目录,执行如下命令

Python代码   收藏代码
  1. hanl@hanl-ubuntu1204:~/j-ci/gettingCase$ mvn clean  
  2. hanl@hanl-ubuntu1204:~/j-ci/gettingCase$ mvnDebug hpi:run  

 Maven将对8000端口执行监听,以便在IDE中进行断点调试

Python代码   收藏代码
  1. Preparing to Execute Maven in Debug Mode  
  2. Listening for transport dt_socket at address: 8000  

 

进入IDE(本例使用IntelliJ),选择Run菜单的Debug,添加一个8000端口的远程服务器

 

添加断点,点击左下角运行调试按钮

此时,终端将执行Maven构建,并启动Jetty服务器.
Python代码   收藏代码
  1. hanl@hanl-ubuntu1204:~/j-ci/gettingCase$ mvnDebug hpi:run  
  2. Preparing to Execute Maven in Debug Mode  
  3. Listening for transport dt_socket at address: 8000  
  4. [INFO] Scanning for projects...  
  5. [INFO]                                                                           
  6. [INFO] ------------------------------------------------------------------------  
  7. [INFO] Building gettingCase 1.0-SNAPSHOT  
  8. [INFO] ------------------------------------------------------------------------  
  9. [INFO]   
  10. [INFO] >>> maven-hpi-plugin:1.95:run (default-cli) @ gettingCase >>>  
  11. [INFO]   
  12. [INFO] --- maven-hpi-plugin:1.95:validate (default-validate) @ gettingCase ---  
  13. [INFO]   
  14. [INFO] --- maven-enforcer-plugin:1.0.1:enforce (enforce-maven) @ gettingCase ---  
  15. [INFO]   
  16. [INFO] --- maven-enforcer-plugin:1.0.1:display-info (display-info) @ gettingCase ---  
  17. [INFO] Maven Version: 3.0.4  
  18. [INFO] JDK Version: 1.7.0_25 normalized as: 1.7.0-25  
  19. [INFO] OS Info: Arch: amd64 Family: unix Name: linux Version: 3.2.0-54-generic  
  20. [INFO]   
  21. [INFO] --- maven-localizer-plugin:1.14:generate (default) @ gettingCase ---  
  22. [INFO]   
  23. [INFO] --- maven-resources-plugin:2.5:resources (default-resources) @ gettingCase ---  
  24. [debug] execute contextualize  
  25. [INFO] Using 'UTF-8' encoding to copy filtered resources.  
  26. [INFO] Copying 5 resources  
  27. [INFO]   
  28. [INFO] --- maven-compiler-plugin:2.5:compile (default-compile) @ gettingCase ---  
  29. [INFO] Nothing to compile - all classes are up to date  
  30. [INFO]   
  31. [INFO] <<< maven-hpi-plugin:1.95:run (default-cli) @ gettingCase <<<  
  32. [INFO]   
  33. [INFO] --- maven-hpi-plugin:1.95:run (default-cli) @ gettingCase ---  
  34. [INFO] Generating ./work/plugins/gettingCase.hpl  
  35. [INFO] Copying dependency Jenkins plugin /home/hanl/.m2/repository/org/jenkins-ci/plugins/mailer/1.4/mailer-1.4.jar  
  36. [INFO] Copying dependency Jenkins plugin /home/hanl/.m2/repository/org/jenkins-ci/plugins/ant/1.1/ant-1.1.jar  
  37. [INFO] Copying dependency Jenkins plugin /home/hanl/.m2/repository/org/jenkins-ci/main/maven-plugin/1.509.3/maven-plugin-1.509.3.jar  
  38. [INFO] Copying dependency Jenkins plugin /home/hanl/.m2/repository/org/jenkins-ci/plugins/javadoc/1.0/javadoc-1.0.jar  
  39. [INFO] Copying dependency Jenkins plugin /home/hanl/.m2/repository/org/jenkins-ci/plugins/subversion/1.26/subversion-1.26.jar  
  40. [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  
  41. [INFO] Configuring Jetty for project: gettingCase  
  42. 2013-10-10 18:10:55.444::INFO:  Logging to STDERR via org.mortbay.log.StdErrLog  
  43. [INFO] Context path = /jenkins  
  44. [INFO] Tmp directory = /home/hanl/j-ci/gettingCase/target/work  
  45. [INFO] Web defaults =  jetty default  
  46. [INFO] Starting jetty 6.1.1 ...  
  47. 2013-10-10 18:10:55.587::INFO:  jetty-6.1.1  
  48. Jenkins home directory: /home/hanl/j-ci/gettingCase/./work found at: System.getProperty("HUDSON_HOME")  
  49. 2013-10-10 18:10:59.572::INFO:  Started SelectChannelConnector @ 0.0.0.0:8080  
  50. [INFO] Started Jetty Server  
  51. [INFO] Console reloading is ENABLED. Hit ENTER on the console to restart the context.  
  52. Oct 102013 6:10:59 PM jenkins.InitReactorRunner$1 onAttained  
  53. INFO: Started initialization  
  54. Oct 102013 6:11:01 PM jenkins.InitReactorRunner$1 onAttained  
  55. INFO: Listed all plugins  
  56. Oct 102013 6:11:01 PM jenkins.InitReactorRunner$1 onAttained  
  57. INFO: Prepared all plugins  
  58. Oct 102013 6:11:01 PM jenkins.InitReactorRunner$1 onAttained  
  59. INFO: Started all plugins  
  60. Oct 102013 6:11:01 PM jenkins.InitReactorRunner$1 onAttained  
  61. INFO: Augmented all extensions  
  62. Oct 102013 6:11:04 PM jenkins.InitReactorRunner$1 onAttained  
  63. INFO: Loaded all jobs  
  64. Oct 102013 6:11:05 PM org.jenkinsci.main.modules.sshd.SSHD start  
  65. INFO: Started SSHD at port 54676  
  66. Oct 102013 6:11:05 PM jenkins.InitReactorRunner$1 onAttained  
  67. INFO: Completed initialization  
  68. Oct 102013 6:11:05 PM hudson.TcpSlaveAgentListener <init>  
  69. INFO: JNLP slave agent listener started on TCP port 47342  
  70. Oct 102013 6:11:05 PM hudson.WebAppMain$2 run  
  71. 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

 

 

Java代码   收藏代码
  1. <f:entry title="Test Station">  
  2. <select class="setting-input" name="AndroidBuilder.stationUrl">  
  3.     <j:forEach var="inst" items="${descriptor.stations}">  
  4.         <f:option selected="${inst.url==instance.stationUrl}">${inst.url}</f:option>  
  5.     </j:forEach>  
  6. </select>  
  7. </f:entry>  

 

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐