第一次做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的工具包
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
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. "Getting Test Cases Builder">
3. "RallyDev User Name" field="userName">
4. <f:textbox />
5. </f:entry>
6. "RallyDev Password" field="password">
7. <f:password/>
8. </f:entry>
9.
10. "HTTP Proxy URL" field="proxyURL">
11. <f:textbox />
12. </f:entry>
13. "RallyDev Proxy User Name" field="proxyUser">
14. <f:textbox />
15. </f:entry>
16. "RallyDev Proxy Password" field="proxyPassword">
17. <f:password />
18. </f:entry>
19. </f:section>
20. </j:jelly>
2.3 编写JOB配置页面
1. 1
2. ${app.displayName}. //应用的Display名称
3.
4. 2
5. ${it.name} 对应于builder的getName()方法
6.
7. 3
/home/hanl/j-ci/gettingCase/src/main/resources/com/technicolor/qcs/gettingCase/GetCasesBuilder/config.jelly
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. "Test Case No." field="testCaseId">
3. <f:textbox />
4. </f:entry>
5. </j: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
1. package
2.
3. import
4. import
5. import
6. import
7. import
8. import
9. import
10. import
11. import
12. import
13. import
14. import
15.
16. import
17. import
18. import
19.
20. /**
21. * author:feuyeux
22. */
23. public class GetCasesBuilder extends
24. public static final String RALLY_URL = "https://rally1.rallydev.com";
25. private final
26.
27. @DataBoundConstructor
28. public
29. this.testCaseId = testCaseId;
30. }
31.
32. public
33. return
34. }
35.
36. @Override
37. public boolean
38. PrintStream out = listener.getLogger();
39. final
40. final
41. final
42. final
43. final
44.
45. "RallyDev User Name ="
46. "HTTP Proxy=" + proxyUser + "@"
47. "RallyDev Test Case =" + getTestCaseId()+"\n");
48. null;
49. try
50. new
51. String caseInfo = rallyClient.getTestCases(testCaseId);
52. out.println(caseInfo);
53. catch
54. out.println(e.getMessage());
55. finally
56. try
57. rallyClient.close();
58. catch
59. e.printStackTrace();
60. }
61. }
62. return true;
63. }
64.
65. @Override
66. public
67. return (DescriptorImpl) super.getDescriptor();
68. }
69.
70. @Extension
71. public static final class DescriptorImpl extends
72. private
73. private
74. private
75. private
76. private
77.
78. public FormValidation doCheckName(@QueryParameter
79. throws
80. if (value.length() == 0)
81. return FormValidation.error("Please set a testCaseId");
82. return
83. }
84.
85. public boolean isApplicable(Class<? extends
86. return true;
87. }
88.
89. public
90. return "Getting Test cases from Rally";
91. }
92.
93. @Override
94. public boolean configure(StaplerRequest req, JSONObject formData) throws
95. "userName");
96. "password");
97. "proxyURL");
98. "proxyUser");
99. "proxyPassword");
100. save();
101. return super.configure(req, formData);
102. }
103.
104. public
105. return
106. }
107.
108. public
109. return
110. }
111.
112. public
113. return
114. }
115.
116. public
117. return
118. }
119.
120. public
121. return
122. }
123. }
124. }
2.5 编写RallyDev连接和访问方法
/home/hanl/j-ci/gettingCase/src/main/java/com/technicolor/qcs/gettingCase/RallyClient.java
1. package
2.
3. import
4. import
5. import
6. import
7. import
8.
9. import
10. import
11. import
12.
13. /**
14. * author:feuyeux
15. */
16. public class
17. /*https://github.com/RallyTools/RallyRestToolkitForJavahttps://github.com/RallyTools/RallyRestToolkitForJava */
18. private final
19.
20. public RallyClient(String rally_url, String userName, String password, String proxyURL, String proxyUser, String proxyPassword) throws
21. new RallyRestApi(new
22. new
23. }
24.
25. public void close() throws
26. restApi.close();
27. }
28.
29. public
30. /*https://rally1.rallydev.com/slm/doc/webservice/*/
31. new
32.
33. String version = restApi.getWsapiVersion();
34. "RallyDev Rest Version=").append(version).append("\n");
35.
36. final String query = "/testcase/"+testCaseId;
37. new
38. null;
39. try
40. casesResponse = restApi.get(queryRequest);
41. catch
42. e.printStackTrace();
43. }
44. printWarningsOrErrors(casesResponse, result);
45. JsonObject caseJsonObject = casesResponse.getObject();
46.
47. "\n").append("Test Case Name: ").append(caseJsonObject.get("Name").getAsString());
48. "\n").append("Test Case Type: ").append(caseJsonObject.get("Type").getAsString());
49. "\n").append("Test Case URL: ").append(caseJsonObject.get("_ref").getAsString());
50. "\n").append("Test Case Creation Time: ").append(caseJsonObject.get("CreationDate").getAsString());
51. "\n").append("Test Case LastUpdate Time: ").append(caseJsonObject.get("LastUpdateDate").getAsString());
52. "\n").append("Test Case's Project: ").append( caseJsonObject.get("Project").getAsJsonObject().get("_refObjectName") .getAsString());
53. "\n").append("Test Case's Workspace: ").append( caseJsonObject.get("Workspace").getAsJsonObject().get("_refObjectName") .getAsString());
54.
55. return
56. }
57.
58. private void
59. if
60. "\nSuccess.");
61. String[] warningList;
62. warningList = response.getWarnings();
63. for (int i = 0; i < warningList.length; i++) {
64. "\twarning:\n"
65. }
66. else
67. String[] errorList;
68. errorList = response.getErrors();
69. if (errorList.length > 0) {
70. "\nError.");
71.
72. }
73. for (int i = 0; i < errorList.length; i++) {
74. "\terror:\n"
75. }
76. }
77. }
78. }
3.调试Plugin程序
在终端/控制台,首先执行Maven变量配置命令
- set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,address=8000,suspend=n
cd到插件项目目录,执行如下命令
1. hanl@hanl-ubuntu1204:~/j-ci/gettingCase$ mvn clean
2. hanl@hanl-ubuntu1204:~/j-ci/gettingCase$ mvnDebug hpi:run
Maven将对8000端口执行监听,以便在IDE中进行断点调试
- Preparing to Execute Maven in
- Listening for transport dt_socket at address: 8000
进入IDE(本例使用IntelliJ),选择Run菜单的Debug,添加一个8000端口的远程服务器
添加断点,点击左下角运行调试按钮
1. hanl@hanl-ubuntu1204:~/j-ci/gettingCase$ mvnDebug hpi:run
2. Preparing to Execute Maven in
3. Listening for transport dt_socket at address: 8000
4. [INFO] Scanning for
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'
26. [INFO] Copying 5
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
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
52. Oct 10, 2013 6:10:59 PM jenkins.InitReactorRunner$1
53. INFO: Started initialization
54. Oct 10, 2013 6:11:01 PM jenkins.InitReactorRunner$1
55. INFO: Listed all plugins
56. Oct 10, 2013 6:11:01 PM jenkins.InitReactorRunner$1
57. INFO: Prepared all plugins
58. Oct 10, 2013 6:11:01 PM jenkins.InitReactorRunner$1
59. INFO: Started all plugins
60. Oct 10, 2013 6:11:01 PM jenkins.InitReactorRunner$1
61. INFO: Augmented all extensions
62. Oct 10, 2013 6:11:04 PM jenkins.InitReactorRunner$1
63. INFO: Loaded all jobs
64. Oct 10, 2013 6:11:05
65. INFO: Started SSHD at port 54676
66. Oct 10, 2013 6:11:05 PM jenkins.InitReactorRunner$1
67. INFO: Completed initialization
68. Oct 10, 2013 6:11:05
69. INFO: JNLP slave agent listener started on TCP port 47342
70. Oct 10, 2013 6:11:05 PM hudson.WebAppMain$2
71. INFO: Jenkins is fully up and
4.测试
在浏览器中录入Jenkins地址,创建Job并按照上述的配置环节,完成配置.
执行Build Job
当完成调试后,进入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
1. <f:entry title="Test Station">
2. <select class="setting-input" name="AndroidBuilder.stationUrl">
3. "inst" items="${descriptor.stations}">
4. "${inst.url==instance.stationUrl}">${inst.url}</f:option>
5. </j:forEach>
6. </select>
7. </f:entry>