├── src ├── main │ ├── webapp │ │ ├── css │ │ │ └── combo-box.css │ │ ├── help-homeDirVarName.html │ │ ├── examples │ │ │ ├── Xcode.groovy │ │ │ ├── UnityOnWindows.groovy │ │ │ └── Unity.groovy │ │ └── help-detectionScript.html │ ├── resources │ │ ├── org │ │ │ └── jenkinsci │ │ │ │ └── plugins │ │ │ │ └── appdetector │ │ │ │ ├── task │ │ │ │ └── template.groovy.vm │ │ │ │ ├── Messages.properties │ │ │ │ ├── AppDetectorParamaterDefinition │ │ │ │ ├── index.jelly │ │ │ │ └── config.jelly │ │ │ │ └── AppDetectorBuildWrapper │ │ │ │ ├── config.jelly │ │ │ │ └── global.jelly │ │ └── index.jelly │ └── java │ │ └── org │ │ └── jenkinsci │ │ └── plugins │ │ └── appdetector │ │ ├── AppUsageSetting.java │ │ ├── AppLabelAtom.java │ │ ├── AppDetectionSetting.java │ │ ├── task │ │ └── AppDetectionTask.java │ │ ├── util │ │ └── Utils.java │ │ ├── AppLabelSet.java │ │ ├── AppDetectorHandler.java │ │ ├── AppDetectorLabelFinder.java │ │ ├── AppDetectorParamaterDefinition.java │ │ └── AppDetectorBuildWrapper.java └── test │ ├── resources │ └── org │ │ └── jenkinsci │ │ └── plugins │ │ └── appdetector │ │ └── task │ │ └── test.groovy │ └── java │ └── org │ └── jenkinsci │ └── plugins │ └── appdetector │ ├── AppLabelSetTest.java │ ├── AppDetectorParamaterDefinitionTest.java │ ├── task │ └── AppDetectionTaskTest.java │ ├── util │ └── UtilsTest.java │ └── AppDetectorHandlerTest.java ├── readme ├── job_config.png ├── global_config.png └── build_parameter.png ├── .gitignore ├── release.properties ├── README.md ├── pom.xml └── config └── google_checks.xml /src/main/webapp/css/combo-box.css: -------------------------------------------------------------------------------- 1 | .comboBoxItem { 2 | color: black; 3 | } 4 | -------------------------------------------------------------------------------- /readme/job_config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/app-detector-plugin/master/readme/job_config.png -------------------------------------------------------------------------------- /readme/global_config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/app-detector-plugin/master/readme/global_config.png -------------------------------------------------------------------------------- /readme/build_parameter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/app-detector-plugin/master/readme/build_parameter.png -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/appdetector/task/template.groovy.vm: -------------------------------------------------------------------------------- 1 | import groovy.json.*; 2 | 3 | def platform = "${platform}"; 4 | 5 | ${scriptBody} 6 | -------------------------------------------------------------------------------- /src/main/resources/index.jelly: -------------------------------------------------------------------------------- 1 | 2 |
3 | Allow to build on a slave with specific applications and versions present 4 |
5 | -------------------------------------------------------------------------------- /src/test/resources/org/jenkinsci/plugins/appdetector/task/test.groovy: -------------------------------------------------------------------------------- 1 | def applist = []; 2 | 3 | applist.add([version: "1.0", home: "/hoge"]); 4 | 5 | return JsonOutput.prettyPrint(JsonOutput.toJson(applist)); 6 | -------------------------------------------------------------------------------- /src/main/webapp/help-homeDirVarName.html: -------------------------------------------------------------------------------- 1 |

2 | Environment variable name for binding application home directory. 3 |

4 |

5 | You can refer to the home directory of the application through variables set during execution of the build. 6 |

7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | 3 | # Mobile Tools for Java (J2ME) 4 | .mtj.tmp/ 5 | 6 | # Package Files # 7 | *.jar 8 | *.war 9 | *.ear 10 | 11 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 12 | hs_err_pid* 13 | 14 | target 15 | work 16 | .DS_Store 17 | -------------------------------------------------------------------------------- /release.properties: -------------------------------------------------------------------------------- 1 | #release configuration 2 | #Mon Nov 19 19:30:43 JST 2018 3 | scm.tagNameFormat=@{project.artifactId}-@{project.version} 4 | pushChanges=true 5 | scm.url=scm\:git\:git@github.com\:jenkinsci/app-detector-plugin.git 6 | preparationGoals=clean install 7 | remoteTagging=true 8 | projectVersionPolicyId=default 9 | scm.commentPrefix=[maven-release-plugin] 10 | exec.additionalArguments= 11 | exec.snapshotReleasePluginAllowed=false 12 | completedPhase=check-poms 13 | -------------------------------------------------------------------------------- /src/main/webapp/examples/Xcode.groovy: -------------------------------------------------------------------------------- 1 | def xcodeList = []; 2 | 3 | def xcodePathList = ["/usr/bin/mdfind", "kMDItemCFBundleIdentifier == 'com.apple.dt.Xcode'"].execute().text.split("\n"); 4 | 5 | xcodePathList.each { 6 | def version = ["env", "DEVELOPER_DIR=" + it, "/usr/bin/xcodebuild", "-version"].execute().text.split("\n")[0].split(" ")[1]; 7 | xcodeList.add([version: version, home: it]); 8 | } 9 | 10 | return JsonOutput.prettyPrint(JsonOutput.toJson(xcodeList)); 11 | -------------------------------------------------------------------------------- /src/main/webapp/examples/UnityOnWindows.groovy: -------------------------------------------------------------------------------- 1 | def unityList = []; 2 | def appDir = "C:\\Program Files"; 3 | 4 | def unityPathList = ["cmd", "/c", "dir", appDir, "/A:d", "/b"].execute().text.split("\r\n").findAll { it.startsWith("Unity")}; 5 | 6 | unityPathList.each { 7 | def version = it.minus("Unity"); 8 | def unityHome = appDir + "\\" + it; 9 | unityList.add([version: version, home: unityHome]); 10 | } 11 | 12 | return JsonOutput.prettyPrint(JsonOutput.toJson(unityList)); 13 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/appdetector/AppUsageSetting.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.appdetector; 2 | 3 | public class AppUsageSetting { 4 | private String appName; 5 | private String version; 6 | 7 | public AppUsageSetting(String appName, String version) { 8 | this.appName = appName; 9 | this.version = version; 10 | } 11 | 12 | public String getAppName() { 13 | return appName; 14 | } 15 | 16 | public String getVersion() { 17 | return version; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/appdetector/Messages.properties: -------------------------------------------------------------------------------- 1 | # Job config 2 | AppDetectorParamaterDefinition_DisplayName=Chois Application Version 3 | JOB_DESCRIPTION=build with the specific application and version 4 | 5 | # label finding 6 | CANNOT_GET_HUDSON_INSTANCE=Could not get any hudson instances 7 | DETECTING_SOFTOWARE_INSTLLATION_FAILED=Detecting the {0} installation was failed on {1}. 8 | 9 | # executing 10 | GETTING_NODE_FAILED=Failed to get the node from this build. 11 | APP_NOT_FOUND=Required application ({0}: {1}) was not found on {2}. 12 | -------------------------------------------------------------------------------- /src/main/webapp/examples/Unity.groovy: -------------------------------------------------------------------------------- 1 | def unityList = []; 2 | def appDir = "/Applications/"; 3 | 4 | def unityPathList = ["ls", appDir].execute().text.split("\n").findAll { it.startsWith("Unity")}; 5 | 6 | unityPathList.each { 7 | def version = ["/usr/libexec/PlistBuddy", "-c", "Print :CFBundleVersion", appDir + it + "/Unity.app/Contents/Info.plist"].execute().text.split("\n")[0]; 8 | def unityHome = appDir + it + "/Unity.app"; 9 | unityList.add([version: version, home: unityHome]); 10 | } 11 | 12 | return JsonOutput.prettyPrint(JsonOutput.toJson(unityList)); 13 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/appdetector/AppDetectorParamaterDefinition/index.jelly: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 |
8 | 9 | 14 |
15 |
16 |
17 | -------------------------------------------------------------------------------- /src/main/webapp/help-detectionScript.html: -------------------------------------------------------------------------------- 1 |

2 | Groovy script to detect which version of which application is installed for each slave. 3 |

4 |

5 | The script MUST return a JSON string of the form: 6 |

7 |
 8 | [
 9 |   {
10 |     "version": "Application Version",
11 |     "home": "Home Directory of This Version"
12 |   },
13 |   ・・・
14 | ]
15 | 
16 |

17 | You can use the 'runExternalCommand()' method to execute external commands and get output.
18 | And you can use the 'platform' variable to determine the platform in the script. ("windows", "linux", or "osx" will be stored) 19 |

20 |

21 | Please refer to the example below. 22 |

26 |

27 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/appdetector/AppDetectorBuildWrapper/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |
17 |
18 |
19 |
20 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/appdetector/AppLabelAtom.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.appdetector; 2 | 3 | import hudson.model.labels.LabelAtom; 4 | 5 | public class AppLabelAtom extends LabelAtom { 6 | 7 | private static final long serialVersionUID = 1L; 8 | 9 | private String application; 10 | private String version; 11 | private String home; 12 | 13 | /** 14 | * Creates new {@link AppLabelAtom} instance. 15 | * @param application The name of application such as "Xcode", "Unity". 16 | * @param version Application version. 17 | * @param home Application home directory. 18 | */ 19 | public AppLabelAtom(String application, String version, String home) { 20 | super(application + "-" + version); 21 | this.application = application; 22 | this.version = version; 23 | this.home = home; 24 | } 25 | 26 | public String getApplication() { 27 | return application; 28 | } 29 | 30 | public String getVersion() { 31 | return version; 32 | } 33 | 34 | public String getHome() { 35 | return home; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/appdetector/AppDetectorParamaterDefinition/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/test/java/org/jenkinsci/plugins/appdetector/AppLabelSetTest.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.appdetector; 2 | 3 | import org.junit.*; 4 | import static org.hamcrest.CoreMatchers.*; 5 | import static org.junit.Assert.*; 6 | 7 | import java.util.Set; 8 | 9 | public class AppLabelSetTest { 10 | private AppLabelSet labels; 11 | 12 | @Before 13 | public void init() { 14 | labels = new AppLabelSet(); 15 | labels.add(new AppLabelAtom("Xcode", "8.0", "/Applications/Xcode.app")); 16 | labels.add(new AppLabelAtom("Xcode", "7.3.1", "/")); 17 | labels.add(new AppLabelAtom("Unity", "5.4.0b24", "/")); 18 | labels.add(new AppLabelAtom("Unity", "5.3.5f1", "/")); 19 | } 20 | 21 | @Test 22 | public void getAppVersions() throws Exception { 23 | Set xcodeVersions = labels.getAppVersions("Xcode"); 24 | 25 | assertThat(xcodeVersions, hasItems("8.0", "7.3.1")); 26 | assertThat(xcodeVersions, not(anyOf(hasItems("5.4.0b24"), hasItems("5.3.5f1")))); 27 | } 28 | 29 | @Test 30 | public void getApplicationLabel() throws Exception { 31 | AppLabelAtom label = labels.getApplicationLabel("Xcode", "8.0"); 32 | 33 | assertThat(label, notNullValue()); 34 | assertThat(label.getApplication(), is("Xcode")); 35 | assertThat(label.getVersion(), is("8.0")); 36 | assertThat(label.getHome(), is("/Applications/Xcode.app")); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/test/java/org/jenkinsci/plugins/appdetector/AppDetectorParamaterDefinitionTest.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.appdetector; 2 | 3 | import mockit.Expectations; 4 | import mockit.Mocked; 5 | import org.junit.*; 6 | import static org.hamcrest.CoreMatchers.*; 7 | import static org.hamcrest.Matchers.contains; 8 | import static org.junit.Assert.*; 9 | import org.jenkinsci.plugins.appdetector.util.Utils; 10 | 11 | import java.util.ArrayList; 12 | import java.util.HashSet; 13 | import java.util.List; 14 | import java.util.Set; 15 | 16 | 17 | public class AppDetectorParamaterDefinitionTest { 18 | private AppDetectorParamaterDefinition param; 19 | 20 | @Mocked 21 | final Utils utils = null; 22 | 23 | @Mocked 24 | private AppLabelSet labels; 25 | 26 | @Before 27 | public void init() { 28 | param = new AppDetectorParamaterDefinition("test", "test", ""); 29 | } 30 | 31 | @Test 32 | public void getSortedVersionList() throws Exception { 33 | 34 | new Expectations() {{ 35 | Utils.getApplicationLabels(); 36 | result = labels; 37 | 38 | labels.getSortedAppVersions("test"); 39 | result = new ArrayList() { 40 | { 41 | add("8.3.1b"); 42 | add("8.3"); 43 | add("8"); 44 | } 45 | }; 46 | }}; 47 | 48 | List actual = param.getSortedVersionList(); 49 | String[] expected = {"8.3.1b", "8.3", "8"}; 50 | assertThat(actual, is(contains(expected))); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/appdetector/AppDetectionSetting.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.appdetector; 2 | 3 | public class AppDetectionSetting { 4 | private String appName; 5 | private String script; 6 | private boolean onLinux; 7 | private boolean onOsx; 8 | private boolean onWindows; 9 | private String homeDirVarName; 10 | 11 | /** 12 | * Construct an AppDetectionSetting Object. 13 | * @param appName Application name to be detected. 14 | * @param script Groovy script used to detect applications. 15 | * @param onLinux Whether to perform detection on Linux. 16 | * @param onOsx Whether to perform detection on Mac. 17 | * @param onWindows Whether to perform detection on Windows. 18 | * @param homeDirVarName Environment variable name to bind the application's home directory. 19 | */ 20 | public AppDetectionSetting(String appName, String script, boolean onLinux, boolean onOsx, 21 | boolean onWindows, String homeDirVarName) { 22 | 23 | this.appName = appName; 24 | this.script = script; 25 | this.onLinux = onLinux; 26 | this.onOsx = onOsx; 27 | this.onWindows = onWindows; 28 | this.homeDirVarName = homeDirVarName; 29 | } 30 | 31 | public String getAppName() { 32 | return appName; 33 | } 34 | 35 | public String getScript() { 36 | return script; 37 | } 38 | 39 | public boolean getOnLinux() { 40 | return onLinux; 41 | } 42 | 43 | public boolean getOnOsx() { 44 | return onOsx; 45 | } 46 | 47 | public boolean getOnWindows() { 48 | return onWindows; 49 | } 50 | 51 | public String getHomeDirVarName() { 52 | return homeDirVarName; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Application Detector Plugin 2 | ==================== 3 | 4 | This plugin provides the following functions. 5 | 6 | - Detects an application and its versions installed in slaves, by using a groovy script. 7 | - Enables you to execute builds by specifying the detected application and its version. 8 | 9 | ## Usage 10 | First, You need to register the detection setting on the Jenkins global setting page, as follows. 11 | 12 | ![](/readme/global_config.png) 13 | 14 | And restart Jenkins, or reconnect slaves to reflect the detection setting. 15 | 16 | **NOTE: Detection setting is not reflected until disconnecting the node and connecting it again.** 17 | 18 | Then, you can specfiy some applications and versions at job setting. 19 | ![](/readme/job_config.png) 20 | 21 | Or, Select it runtime by using "Choice Application Version" build parameter. 22 | ![](/readme/build_parameter.png) 23 | 24 | 25 | ### About Detection Script 26 | - Script MUST return a JSON string of the form: 27 | ```json 28 | [ 29 | { 30 | "version": "Application Version", 31 | "home": "Home Directory of This Version" 32 | }, 33 | ] 34 | ``` 35 | - You can use the '[cmd, arg1, arg2 ...].execute().text' method to execute external commands and get output. 36 | - And you can use the 'platform' variable to determine the platform in the script. ("windows", "linux", or "osx" will be stored) 37 | 38 | #### Sample 39 | Here is some sample scripts. 40 | 41 | - [Unity](/src/main/webapp/examples/Unity.groovy) 42 | - [Unity(on Windows)](/src/main/webapp/examples/UnityOnWindows.groovy) 43 | - [Xcode](/src/main/webapp/examples/Xcode.groovy) 44 | 45 | ## Licence 46 | 47 | [MIT](https://github.com/tcnksm/tool/blob/master/LICENCE) 48 | 49 | ## Author 50 | 51 | [justice3120](https://github.com/justice3120) 52 | -------------------------------------------------------------------------------- /src/test/java/org/jenkinsci/plugins/appdetector/task/AppDetectionTaskTest.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.appdetector.task; 2 | 3 | import mockit.Expectations; 4 | import mockit.Mocked; 5 | import org.jenkinsci.plugins.appdetector.AppDetectionSetting; 6 | import org.jenkinsci.plugins.appdetector.util.Utils; 7 | import org.junit.*; 8 | import static org.hamcrest.MatcherAssert.*; 9 | import static org.hamcrest.Matchers.*; 10 | import net.sf.json.JSONArray; 11 | import net.sf.json.JSONObject; 12 | 13 | import java.util.Scanner; 14 | import java.util.Set; 15 | import java.io.InputStream; 16 | 17 | public class AppDetectionTaskTest { 18 | 19 | private AppDetectionTask task; 20 | 21 | @Mocked 22 | final Utils utils = null; 23 | 24 | @Before 25 | public void init() { 26 | InputStream in = this.getClass().getResourceAsStream("test.groovy"); 27 | String script = new Scanner(in, "UTF-8").useDelimiter("\\A").next(); 28 | AppDetectionSetting setting = new AppDetectionSetting("Test", script, false, true, false, "TEST"); 29 | task = new AppDetectionTask(setting); 30 | } 31 | 32 | @Test 33 | public void call() throws Exception { 34 | 35 | new Expectations() {{ 36 | Utils.runExternalCommand("uname"); 37 | result = "Darwin\n"; 38 | }}; 39 | 40 | JSONArray result = JSONArray.fromObject(task.call()); 41 | JSONObject expectedItem = new JSONObject(); 42 | expectedItem.put("version", "1.0"); 43 | expectedItem.put("home", "/hoge"); 44 | 45 | assertThat(result, hasItem(expectedItem)); 46 | } 47 | 48 | @Test 49 | public void callWhenPlatformNotMatched() throws Exception { 50 | 51 | new Expectations() {{ 52 | Utils.runExternalCommand("uname"); 53 | result = "Linux\n"; 54 | }}; 55 | 56 | JSONArray result = JSONArray.fromObject(task.call()); 57 | 58 | assertThat(result, is(empty())); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/appdetector/AppDetectorBuildWrapper/global.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |

Note: Detection setting is not reflected until disconnecting the node and connecting it again.

8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | Linux 21 | 22 | OSX 23 | 24 | Windows 25 | 26 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 |
40 |
41 |
42 |
43 | 44 |
45 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/appdetector/task/AppDetectionTask.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.appdetector.task; 2 | 3 | import groovy.lang.GroovyShell; 4 | import jenkins.security.MasterToSlaveCallable; 5 | import org.apache.velocity.VelocityContext; 6 | import org.apache.velocity.app.Velocity; 7 | import org.jenkinsci.plugins.appdetector.AppDetectionSetting; 8 | import org.jenkinsci.plugins.appdetector.util.Utils; 9 | 10 | import java.io.InputStream; 11 | import java.io.StringWriter; 12 | import java.net.URL; 13 | import java.util.Scanner; 14 | 15 | public class AppDetectionTask extends MasterToSlaveCallable { 16 | private String appName; 17 | private String scriptString; 18 | private boolean onLinux; 19 | private boolean onOsx; 20 | private boolean onWindows; 21 | 22 | /** 23 | * Construct an AppDetectionTask Object. 24 | * @param setting Detection method setting. 25 | */ 26 | public AppDetectionTask(AppDetectionSetting setting) { 27 | this.appName = setting.getAppName(); 28 | this.scriptString = setting.getScript(); 29 | this.onLinux = setting.getOnLinux(); 30 | this.onOsx = setting.getOnOsx(); 31 | this.onWindows = setting.getOnWindows(); 32 | } 33 | 34 | @Override 35 | public String call() throws Exception { 36 | String result = "[]"; 37 | String platform = getPlatform(); 38 | 39 | if ("osx".equals(platform)) { 40 | if (! onOsx) { 41 | return result; 42 | } 43 | } else if ("linux".equals(platform)) { 44 | if (! onLinux) { 45 | return result; 46 | } 47 | } else { 48 | if (! onWindows) { 49 | return result; 50 | } 51 | } 52 | 53 | String templateString = loadTemplateFile(); 54 | 55 | StringWriter writer = new StringWriter(); 56 | VelocityContext context = new VelocityContext(); 57 | context.put("platform", platform); 58 | context.put("scriptBody", scriptString); 59 | 60 | Velocity.evaluate(context, writer, "", templateString); 61 | 62 | GroovyShell shell = new GroovyShell(this.getClass().getClassLoader()); 63 | groovy.lang.Script script = shell.parse(writer.toString()); 64 | result = (String) script.run(); 65 | 66 | return result; 67 | } 68 | 69 | private String getPlatform() { 70 | try { 71 | String uname = Utils.runExternalCommand("uname").replace("\n",""); 72 | if ("Darwin".equals(uname)) { 73 | return "osx"; 74 | } 75 | return "linux"; 76 | } catch (Exception e) { 77 | return "windows"; 78 | } 79 | } 80 | 81 | private String loadTemplateFile() throws Exception { 82 | URL templateUrl = AppDetectionTask.class.getResource("template.groovy.vm"); 83 | InputStream in = templateUrl.openConnection().getInputStream(); 84 | String templateString = new Scanner(in, "UTF-8").useDelimiter("\\A").next(); 85 | return templateString; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/test/java/org/jenkinsci/plugins/appdetector/util/UtilsTest.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.appdetector.util; 2 | 3 | import hudson.model.Computer; 4 | import hudson.model.Node; 5 | import mockit.Expectations; 6 | import mockit.Mocked; 7 | import org.junit.*; 8 | import static org.hamcrest.CoreMatchers.*; 9 | import static org.junit.Assert.*; 10 | import org.jenkinsci.plugins.appdetector.AppLabelAtom; 11 | import org.jenkinsci.plugins.appdetector.AppLabelSet; 12 | import jenkins.model.Jenkins; 13 | 14 | import java.util.Arrays; 15 | import java.util.Map; 16 | import java.util.HashMap; 17 | import java.util.HashSet; 18 | import java.util.Set; 19 | 20 | public class UtilsTest { 21 | @Mocked 22 | private Jenkins jenkins; 23 | 24 | @Mocked 25 | private Computer computer; 26 | 27 | @Mocked 28 | private Node node; 29 | 30 | @Mocked 31 | private AppLabelAtom label; 32 | 33 | @Test 34 | public void expandVariablesWithBuildVarToken() throws Exception { 35 | Map buildVers = new HashMap(); 36 | buildVers.put("hoge", "8.0"); 37 | String token = "${hoge}"; 38 | 39 | String expanded = Utils.expandVariables(buildVers, token); 40 | 41 | assertThat(expanded, is("8.0")); 42 | } 43 | 44 | @Test 45 | public void expandVariablesWithPlainToken() { 46 | Map buildVers = new HashMap(); 47 | buildVers.put("hoge", "8.0"); 48 | String token = "hoge"; 49 | 50 | String expanded = Utils.expandVariables(buildVers, token); 51 | 52 | assertThat(expanded, is("hoge")); 53 | } 54 | 55 | @Test 56 | public void getAllComputers() throws Exception { 57 | 58 | new Expectations() {{ 59 | Jenkins.getInstance(); 60 | result = jenkins; 61 | 62 | jenkins.getComputers(); 63 | result = new Computer[] { computer }; 64 | }}; 65 | 66 | Computer[] allComputers = Utils.getAllComputers(); 67 | assertThat(allComputers.length, is(1)); 68 | assertThat(Arrays.asList(allComputers), hasItem(computer)); 69 | } 70 | 71 | @Test 72 | public void getApplicationLabels() throws Exception { 73 | final Set assignedLabels = new HashSet(); 74 | assignedLabels.add(label); 75 | 76 | new Expectations() {{ 77 | Jenkins.getInstance(); 78 | result = jenkins; 79 | 80 | jenkins.getComputers(); 81 | result = new Computer[] { computer }; 82 | 83 | computer.getNode(); 84 | result = node; 85 | 86 | node.getAssignedLabels(); 87 | result = assignedLabels; 88 | }}; 89 | 90 | AppLabelSet labels = Utils.getApplicationLabels(); 91 | assertThat(labels.size(), is(1)); 92 | assertThat(labels, hasItem(label)); 93 | } 94 | 95 | @Test 96 | public void getApplicationLabelsWithNode() throws Exception { 97 | final Set assignedLabels = new HashSet(); 98 | assignedLabels.add(label); 99 | 100 | new Expectations() { 101 | { 102 | node.getAssignedLabels(); 103 | result = assignedLabels; 104 | } 105 | }; 106 | 107 | AppLabelSet labels = Utils.getApplicationLabels(node); 108 | assertThat(labels.size(), is(1)); 109 | assertThat(labels, hasItem(label)); 110 | } 111 | 112 | @Test 113 | public void runExternalCommand() throws Exception { 114 | String output = Utils.runExternalCommand("echo", "HOGE"); 115 | assertThat(output, is("HOGE\n")); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/appdetector/util/Utils.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.appdetector.util; 2 | 3 | import hudson.Util; 4 | import hudson.model.Computer; 5 | import hudson.model.Node; 6 | import hudson.model.labels.LabelAtom; 7 | import jenkins.model.Jenkins; 8 | import org.jenkinsci.plugins.appdetector.AppLabelAtom; 9 | import org.jenkinsci.plugins.appdetector.AppLabelSet; 10 | import org.zeroturnaround.exec.ProcessExecutor; 11 | 12 | import java.util.HashMap; 13 | import java.util.Map; 14 | import java.util.Set; 15 | 16 | public class Utils { 17 | 18 | /** 19 | * Expands the variable in the given string to its value in the variables available to this build. 20 | * 21 | * @param buildVars Map of the build-specific variables. 22 | * @param token The token which may or may not contain variables in the format ${foo}. 23 | * @return The given token, with applicable variable expansions done. 24 | */ 25 | public static String expandVariables(Map buildVars, String token) { 26 | 27 | final Map vars = new HashMap(); 28 | if (buildVars != null) { 29 | // Build-specific variables, if any, take priority over environment variables 30 | vars.putAll(buildVars); 31 | } 32 | 33 | String result = Util.fixEmptyAndTrim(token); 34 | if (result != null) { 35 | result = Util.replaceMacro(result, vars); 36 | } 37 | return Util.fixEmptyAndTrim(result); 38 | } 39 | 40 | /** 41 | * Get all Computers of Jenkins including master. 42 | * @return List of Computer 43 | */ 44 | public static Computer[] getAllComputers() { 45 | Jenkins jenkins = Jenkins.getInstance(); 46 | 47 | if (jenkins == null) { 48 | return new Computer[0]; 49 | } 50 | 51 | return jenkins.getComputers(); 52 | } 53 | 54 | /** 55 | * Getting application labels from all nodes that connected to jenkins. 56 | * @return all of application labels. 57 | */ 58 | public static AppLabelSet getApplicationLabels() { 59 | AppLabelSet applicationLabels = new AppLabelSet(); 60 | 61 | Computer[] allComputers = getAllComputers(); 62 | for (Computer computer: allComputers) { 63 | Node node = computer.getNode(); 64 | if (node != null) { 65 | applicationLabels.addAll(getApplicationLabels(node)); 66 | } 67 | } 68 | return applicationLabels; 69 | } 70 | 71 | /** 72 | * Getting application labels from specified node. 73 | * @param node The target node. 74 | * @return Application labels that assigned to given node. 75 | */ 76 | public static AppLabelSet getApplicationLabels(Node node) { 77 | AppLabelSet applicationLabels = new AppLabelSet(); 78 | 79 | Set allLabels = node.getAssignedLabels(); 80 | for (LabelAtom label: allLabels) { 81 | if (label instanceof AppLabelAtom) { 82 | applicationLabels.add((AppLabelAtom)label); 83 | } 84 | } 85 | 86 | return applicationLabels; 87 | } 88 | 89 | /** 90 | * Executing external command, and get output. 91 | * @param command The list containing the program and its arguments. 92 | * @return output string. 93 | * @throws Exception Fail to execute command. 94 | */ 95 | public static String runExternalCommand(String... command) throws Exception { 96 | String output = new ProcessExecutor().command(command).readOutput(true).execute().outputUTF8(); 97 | if (output == null) { 98 | output = ""; 99 | } 100 | 101 | return output; 102 | } 103 | 104 | } 105 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/appdetector/AppLabelSet.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.appdetector; 2 | 3 | import java.io.Serializable; 4 | import java.util.ArrayList; 5 | import java.util.Collections; 6 | import java.util.Comparator; 7 | import java.util.HashSet; 8 | import java.util.List; 9 | import java.util.Set; 10 | import java.util.TreeSet; 11 | 12 | public class AppLabelSet extends HashSet { 13 | 14 | /** 15 | * Returns a Set of the app name strings contained in this set. 16 | * @return app name set. 17 | */ 18 | public Set getAppNames() { 19 | Set appNames = new TreeSet(); 20 | for (AppLabelAtom label: this) { 21 | appNames.add(label.getApplication()); 22 | } 23 | return appNames; 24 | } 25 | 26 | /** 27 | * Returns a Set of the app version strings contained in this set. 28 | * @param appName The application name for which you want to obtain the version list. 29 | * @return app version set. 30 | */ 31 | public Set getAppVersions(String appName) { 32 | Set appVersions = new TreeSet(); 33 | for (AppLabelAtom label: this) { 34 | if (appName.equals(label.getApplication())) { 35 | appVersions.add(label.getVersion()); 36 | } 37 | } 38 | return appVersions; 39 | } 40 | 41 | /** 42 | * Returns the version list sorted in DESC. 43 | * @param appName The application name for which you want to obtain the version list. 44 | * @return The version list sorted in DESC 45 | */ 46 | public List getSortedAppVersions(String appName) { 47 | List versionList 48 | = new ArrayList(getAppVersions(appName)); 49 | Collections.sort(versionList, new VersionComparator()); 50 | Collections.reverse(versionList); 51 | return versionList; 52 | } 53 | 54 | /** 55 | * Returns a application label that matches specified condition, 56 | * or null if all labels not matched. 57 | * @param application The name of application such as "Xcode", "Unity". 58 | * @param version Application version. 59 | * @return The mached application label or null. 60 | */ 61 | public AppLabelAtom getApplicationLabel(String application, String version) { 62 | for (AppLabelAtom label: this) { 63 | if (label.getApplication().equals(application) && label.getVersion().equals(version)) { 64 | return label; 65 | } 66 | } 67 | return null; 68 | } 69 | 70 | private static class VersionComparator implements Comparator, Serializable { 71 | private static final long serialVersionUID = 1L; 72 | 73 | public int compare(String verString1, String verString2) { 74 | String[] verArray1 = verString1.split("\\."); 75 | String[] verArray2 = verString2.split("\\."); 76 | 77 | for (int i = 0; i < verArray1.length; i++) { 78 | try { 79 | try { 80 | int num1 = Integer.parseInt(verArray1[i]); 81 | int num2 = Integer.parseInt(verArray2[i]); 82 | 83 | if (! (num1 == num2)) { 84 | return (num1 - num2); 85 | } 86 | } catch (NumberFormatException e) { 87 | String num1 = verArray1[i]; 88 | String num2 = verArray2[i]; 89 | 90 | if (! num1.equals(num2)) { 91 | return num1.compareTo(num2); 92 | } 93 | } 94 | } catch (ArrayIndexOutOfBoundsException e) { 95 | return 1; 96 | } 97 | } 98 | 99 | return (verArray1.length - verArray2.length); 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/appdetector/AppDetectorHandler.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.appdetector; 2 | 3 | import hudson.Extension; 4 | import hudson.matrix.Combination; 5 | import hudson.matrix.MatrixConfiguration; 6 | import hudson.model.Action; 7 | import hudson.model.Label; 8 | import hudson.model.ParameterValue; 9 | import hudson.model.ParametersAction; 10 | import hudson.model.Project; 11 | import hudson.model.Queue; 12 | import hudson.model.labels.LabelAssignmentAction; 13 | import hudson.model.labels.LabelAtom; 14 | import hudson.model.queue.SubTask; 15 | import hudson.tasks.BuildWrapper; 16 | import org.jenkinsci.plugins.appdetector.util.Utils; 17 | 18 | import java.util.HashMap; 19 | import java.util.List; 20 | import java.util.Map; 21 | import java.util.TreeMap; 22 | 23 | @Extension(ordinal = -100) 24 | public class AppDetectorHandler extends Queue.QueueDecisionHandler { 25 | @Override 26 | public boolean shouldSchedule(Queue.Task task, List actions) { 27 | if (task instanceof Project) { 28 | List buildWapperList = ((Project)task).getBuildWrappersList(); 29 | 30 | for (BuildWrapper bw: buildWapperList) { 31 | if (bw instanceof AppDetectorBuildWrapper) { 32 | List settings = ((AppDetectorBuildWrapper)bw).getAppUsageSettings(); 33 | 34 | if (settings.isEmpty()) { 35 | return true; 36 | } 37 | 38 | final Map buildVars = new TreeMap(); 39 | 40 | if (task instanceof MatrixConfiguration) { 41 | Combination combination = ((MatrixConfiguration)task).getCombination(); 42 | buildVars.putAll(combination); 43 | } 44 | 45 | buildVars.putAll(getBuildVariablesFromActions(actions)); 46 | 47 | ApplicationLabelAssignmentAction action = new ApplicationLabelAssignmentAction(); 48 | 49 | for (AppUsageSetting setting: settings) { 50 | String expandedVersion = Utils.expandVariables(buildVars, setting.getVersion()); 51 | 52 | action.add(setting.getAppName() + "-" + expandedVersion); 53 | } 54 | actions.add(action); 55 | } 56 | } 57 | } 58 | return true; 59 | } 60 | 61 | private Map getBuildVariablesFromActions(List actions) { 62 | Map buildVars = new HashMap(); 63 | for (Action action: actions) { 64 | if (action instanceof ParametersAction) { 65 | for (ParameterValue param: ((ParametersAction)action).getParameters()) { 66 | if (param.getValue() != null) { 67 | buildVars.put(param.getName(), param.getValue().toString()); 68 | } 69 | } 70 | } 71 | } 72 | return buildVars; 73 | } 74 | 75 | private static class ApplicationLabelAssignmentAction implements LabelAssignmentAction { 76 | private Label label; 77 | 78 | public ApplicationLabelAssignmentAction() { 79 | this.label = null; 80 | } 81 | 82 | public Label getAssignedLabel(SubTask task) { 83 | Label taskLabel = task.getAssignedLabel(); 84 | 85 | if (taskLabel != null) { 86 | return label.and(taskLabel); 87 | } 88 | 89 | return label; 90 | } 91 | 92 | public void add(String labelString) { 93 | if (label == null) { 94 | label = new LabelAtom(labelString); 95 | } else { 96 | label = label.and(new LabelAtom(labelString)); 97 | } 98 | } 99 | 100 | public String getIconFileName() { 101 | return null; 102 | } 103 | 104 | public String getDisplayName() { 105 | return null; 106 | } 107 | 108 | public String getUrlName() { 109 | return null; 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/appdetector/AppDetectorLabelFinder.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.appdetector; 2 | 3 | import hudson.Extension; 4 | import hudson.model.Computer; 5 | import hudson.model.LabelFinder; 6 | import hudson.model.Node; 7 | import hudson.model.TaskListener; 8 | import hudson.model.labels.LabelAtom; 9 | import hudson.slaves.ComputerListener; 10 | import jenkins.model.Jenkins; 11 | import net.sf.json.JSONArray; 12 | import net.sf.json.JSONObject; 13 | import org.jenkinsci.plugins.appdetector.task.AppDetectionTask; 14 | 15 | import java.util.Collection; 16 | import java.util.Collections; 17 | import java.util.HashSet; 18 | import java.util.List; 19 | import java.util.Map; 20 | import java.util.Set; 21 | import java.util.concurrent.ConcurrentHashMap; 22 | import java.util.logging.LogManager; 23 | import java.util.logging.Logger; 24 | 25 | @Extension 26 | public class AppDetectorLabelFinder extends LabelFinder { 27 | 28 | private final Map> cashedLabels 29 | = new ConcurrentHashMap>(); 30 | 31 | @Override 32 | public Collection findLabels(Node node) { 33 | Computer computer = node.toComputer(); 34 | if (computer == null || node.getChannel() == null) { 35 | return Collections.emptyList(); 36 | } 37 | 38 | Set applications = cashedLabels.get(node); 39 | if (applications == null || applications.isEmpty()) { 40 | return Collections.emptyList(); 41 | } 42 | 43 | return applications; 44 | } 45 | 46 | @Extension 47 | public static class AppDetectorComputerListener extends ComputerListener { 48 | 49 | @Override 50 | public void onOnline(Computer computer, TaskListener taskListener) { 51 | Set applications = detectInstalledApplications(computer); 52 | if (!applications.isEmpty()) { 53 | finder().cashedLabels.put(computer.getNode(), applications); 54 | } else { 55 | finder().cashedLabels.remove(computer.getNode()); 56 | } 57 | } 58 | 59 | @Override 60 | public void onConfigurationChange() { 61 | AppDetectorLabelFinder finder = finder(); 62 | 63 | Set cachedNodes = new HashSet(finder.cashedLabels.keySet()); 64 | 65 | Jenkins jenkins = Jenkins.getInstance(); 66 | 67 | if (jenkins == null) { 68 | return; 69 | } 70 | 71 | List realNodes = jenkins.getNodes(); 72 | for (Node node: cachedNodes) { 73 | if (!realNodes.contains(node)) { 74 | finder.cashedLabels.remove(node); 75 | } 76 | } 77 | } 78 | 79 | private AppDetectorLabelFinder finder() { 80 | return LabelFinder.all().get(AppDetectorLabelFinder.class); 81 | } 82 | 83 | private Set detectInstalledApplications(Computer computer) { 84 | Logger logger = LogManager.getLogManager().getLogger("hudson.WebAppMain"); 85 | 86 | Set applications = new HashSet(); 87 | 88 | Jenkins hudsonInstance = Jenkins.getInstance(); 89 | if (hudsonInstance == null) { 90 | logger.warning(Messages.CANNOT_GET_HUDSON_INSTANCE()); 91 | return applications; 92 | } 93 | 94 | AppDetectorBuildWrapper.DescriptorImpl descriptor = 95 | hudsonInstance.getDescriptorByType(AppDetectorBuildWrapper.DescriptorImpl.class); 96 | 97 | Boolean isUnix = computer.isUnix(); 98 | //This computer seems offline. So, skip detection. 99 | if (isUnix == null) { 100 | return applications; 101 | } 102 | 103 | for (AppDetectionSetting setting: descriptor.getDetectionSettings()) { 104 | try { 105 | AppDetectionTask task = new AppDetectionTask(setting); 106 | 107 | String result = computer.getChannel().call(task); 108 | JSONArray appVersions = JSONArray.fromObject(result); 109 | 110 | for (Object appInfo: appVersions) { 111 | JSONObject info = JSONObject.fromObject(appInfo); 112 | applications.add( 113 | new AppLabelAtom(setting.getAppName(), info.getString("version"), 114 | info.getString("home"))); 115 | } 116 | } catch (Exception e) { 117 | logger.warning( 118 | Messages.DETECTING_SOFTOWARE_INSTLLATION_FAILED(setting.getAppName(), 119 | computer.getDisplayName())); 120 | e.printStackTrace(); 121 | } 122 | } 123 | return applications; 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/appdetector/AppDetectorParamaterDefinition.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.appdetector; 2 | 3 | import hudson.Extension; 4 | import hudson.model.ParameterDefinition; 5 | import hudson.model.ParameterValue; 6 | import hudson.model.SimpleParameterDefinition; 7 | import hudson.model.StringParameterValue; 8 | import hudson.util.ComboBoxModel; 9 | import hudson.util.ListBoxModel; 10 | import net.sf.json.JSONObject; 11 | import org.jenkinsci.plugins.appdetector.util.Utils; 12 | import org.kohsuke.stapler.DataBoundConstructor; 13 | import org.kohsuke.stapler.QueryParameter; 14 | import org.kohsuke.stapler.StaplerRequest; 15 | import org.kohsuke.stapler.export.Exported; 16 | 17 | import java.util.ArrayList; 18 | import java.util.List; 19 | 20 | public class AppDetectorParamaterDefinition extends SimpleParameterDefinition { 21 | 22 | private final String appName; 23 | private List choices; 24 | private final String defaultValue; 25 | 26 | /** 27 | * Creates new {@link AppDetectorParamaterDefinition} instance. 28 | * @param name Name. 29 | * @param appName The name of application such as "Xcode", "Unity". 30 | * @param description Description. 31 | */ 32 | //@DataBoundConstructor 33 | public AppDetectorParamaterDefinition(String name, String appName, String description) { 34 | super(name, description); 35 | this.appName = appName; 36 | this.choices = getSortedVersionList(); 37 | defaultValue = null; 38 | } 39 | 40 | /** 41 | * Creates new {@link AppDetectorParamaterDefinition} instance. 42 | * @param name Name. 43 | * @param appName The name of application such as "Xcode", "Unity". 44 | * @param defaultValue Default version of this application. 45 | * @param description Description. 46 | */ 47 | @DataBoundConstructor 48 | public AppDetectorParamaterDefinition(String name, String appName, String defaultValue, 49 | String description) { 50 | super(name, description); 51 | this.appName = appName; 52 | this.choices = getSortedVersionList(); 53 | this.defaultValue = defaultValue; 54 | } 55 | 56 | @Override 57 | public ParameterDefinition copyWithDefaultValue(ParameterValue defaultValue) { 58 | if (defaultValue instanceof StringParameterValue) { 59 | StringParameterValue value = (StringParameterValue) defaultValue; 60 | return new AppDetectorParamaterDefinition(getName(), getAppName(), value.value, 61 | getDescription()); 62 | } else { 63 | return this; 64 | } 65 | } 66 | 67 | @Exported 68 | public String getAppName() { 69 | return appName; 70 | } 71 | 72 | @Exported 73 | public String getDefaultValue() { 74 | return defaultValue; 75 | } 76 | 77 | /** 78 | * Returns the version list sorted in DESC. 79 | * @return The version list sorted in DESC 80 | */ 81 | public List getSortedVersionList() { 82 | return Utils.getApplicationLabels().getSortedAppVersions(appName); 83 | } 84 | 85 | @Override 86 | public StringParameterValue getDefaultParameterValue() { 87 | // Update option to current version list 88 | choices = new ArrayList(Utils.getApplicationLabels().getSortedAppVersions(appName)); 89 | if (choices.contains(defaultValue)) { 90 | return new StringParameterValue(getName(), defaultValue, getDescription()); 91 | } else { 92 | return new StringParameterValue(getName(), choices.get(0), getDescription()); 93 | } 94 | } 95 | 96 | private StringParameterValue checkValue(StringParameterValue value) { 97 | // Update option to current version list 98 | choices = new ArrayList(Utils.getApplicationLabels().getSortedAppVersions(appName)); 99 | if (!choices.contains(value.value)) { 100 | throw new IllegalArgumentException("Illegal choice for parameter " + getName() + ": " 101 | + value.value); 102 | } 103 | return value; 104 | } 105 | 106 | @Override 107 | public ParameterValue createValue(StaplerRequest req, JSONObject jo) { 108 | StringParameterValue value = req.bindJSON(StringParameterValue.class, jo); 109 | value.setDescription(getDescription()); 110 | return checkValue(value); 111 | } 112 | 113 | public StringParameterValue createValue(String value) { 114 | return checkValue(new StringParameterValue(getName(), value, getDescription())); 115 | } 116 | 117 | @Extension 118 | public static class DescriptorImpl extends ParameterDescriptor { 119 | @Override 120 | public String getDisplayName() { 121 | return Messages.AppDetectorParamaterDefinition_DisplayName(); 122 | } 123 | 124 | /** 125 | * Fill in the value of the select element of the application name in Jenkins' Web view. 126 | * @return List of application names. 127 | */ 128 | public ListBoxModel doFillAppNameItems() { 129 | ListBoxModel items = new ListBoxModel(); 130 | AppLabelSet labels = Utils.getApplicationLabels(); 131 | for (String appName: labels.getAppNames()) { 132 | items.add(appName); 133 | } 134 | return items; 135 | } 136 | 137 | public ComboBoxModel doFillDefaultValueItems(@QueryParameter("appName") final String appName) { 138 | AppLabelSet labels = Utils.getApplicationLabels(); 139 | return new ComboBoxModel(labels.getSortedAppVersions(appName)); 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/test/java/org/jenkinsci/plugins/appdetector/AppDetectorHandlerTest.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.appdetector; 2 | 3 | import org.junit.*; 4 | import static org.hamcrest.MatcherAssert.*; 5 | import static org.hamcrest.Matchers.*; 6 | import mockit.Expectations; 7 | import mockit.Mocked; 8 | import hudson.matrix.Combination; 9 | import hudson.matrix.MatrixConfiguration; 10 | import hudson.model.Action; 11 | import hudson.model.Descriptor; 12 | import hudson.model.StringParameterValue; 13 | import hudson.model.ParametersAction; 14 | import hudson.model.ParameterValue; 15 | import hudson.model.Project; 16 | import hudson.model.StringParameterValue; 17 | import hudson.model.Label; 18 | import hudson.model.labels.LabelAssignmentAction; 19 | import hudson.model.labels.LabelAtom; 20 | import hudson.tasks.BuildWrapper; 21 | import hudson.util.DescribableList; 22 | 23 | import java.util.Arrays; 24 | import java.util.ArrayList; 25 | import java.util.HashMap; 26 | import java.util.List; 27 | import java.io.PrintStream; 28 | 29 | public class AppDetectorHandlerTest { 30 | private AppDetectorHandler handler; 31 | 32 | @Mocked 33 | private Project task; 34 | 35 | @Mocked 36 | private MatrixConfiguration matrixTask; 37 | 38 | @Mocked 39 | private AppDetectorBuildWrapper bw; 40 | 41 | private List actions; 42 | 43 | @Before 44 | public void init() { 45 | actions = new ArrayList(); 46 | handler = new AppDetectorHandler(); 47 | } 48 | 49 | @Test 50 | public void shouldSchedule() throws Exception { 51 | new Expectations() { 52 | { 53 | task.getBuildWrappersList(); 54 | result = new DescribableList>() { 55 | { 56 | add(bw); 57 | } 58 | }; 59 | 60 | task.getAssignedLabel(); 61 | result = null; 62 | 63 | bw.getAppUsageSettings(); 64 | result = new ArrayList<>(Arrays.asList(new AppUsageSetting("Xcode", "8.0"))); 65 | } 66 | }; 67 | LabelAssignmentAction actualAction = null; 68 | boolean result = handler.shouldSchedule(task, actions); 69 | 70 | for (Action action: actions) { 71 | if (action instanceof LabelAssignmentAction) { 72 | actualAction = (LabelAssignmentAction) action; 73 | break; 74 | } 75 | } 76 | 77 | assertThat(result, is(true)); 78 | assertThat(actualAction, is(notNullValue())); 79 | 80 | Label expectedLabel = new LabelAtom("Xcode-8.0"); 81 | assertThat(actualAction.getAssignedLabel(task), is(expectedLabel)); 82 | } 83 | 84 | @Test 85 | public void shouldScheduleWhenMultipleActionAssigned() throws Exception { 86 | new Expectations() { 87 | { 88 | task.getBuildWrappersList(); 89 | result = new DescribableList>() { 90 | { 91 | add(bw); 92 | } 93 | }; 94 | 95 | task.getAssignedLabel(); 96 | result = null; 97 | 98 | bw.getAppUsageSettings(); 99 | result = new ArrayList<>(Arrays.asList( 100 | new AppUsageSetting("Xcode", "8.0"), new AppUsageSetting("Unity", "5.3.6f1"))); 101 | } 102 | }; 103 | LabelAssignmentAction actualAction = null; 104 | boolean result = handler.shouldSchedule(task, actions); 105 | 106 | for (Action action: actions) { 107 | if (action instanceof LabelAssignmentAction) { 108 | actualAction = (LabelAssignmentAction) action; 109 | break; 110 | } 111 | } 112 | 113 | assertThat(result, is(true)); 114 | assertThat(actualAction, is(notNullValue())); 115 | 116 | Label expectedLabel = new LabelAtom("Xcode-8.0"); 117 | expectedLabel = expectedLabel.and(new LabelAtom("Unity-5.3.6f1")); 118 | assertThat(actualAction.getAssignedLabel(task), is(expectedLabel)); 119 | } 120 | 121 | @Test 122 | public void shouldScheduleWhenProjectLabelAssigned() throws Exception { 123 | new Expectations() { 124 | { 125 | task.getBuildWrappersList(); 126 | result = new DescribableList>() { 127 | { 128 | add(bw); 129 | } 130 | }; 131 | 132 | task.getAssignedLabel(); 133 | result = new LabelAtom("master"); 134 | 135 | bw.getAppUsageSettings(); 136 | result = new ArrayList<>(Arrays.asList(new AppUsageSetting("Xcode", "8.0"))); 137 | } 138 | }; 139 | LabelAssignmentAction actualAction = null; 140 | boolean result = handler.shouldSchedule(task, actions); 141 | 142 | for (Action action: actions) { 143 | if (action instanceof LabelAssignmentAction) { 144 | actualAction = (LabelAssignmentAction) action; 145 | break; 146 | } 147 | } 148 | 149 | assertThat(result, is(true)); 150 | assertThat(actualAction, is(notNullValue())); 151 | 152 | Label expectedLabel = new LabelAtom("Xcode-8.0"); 153 | expectedLabel = expectedLabel.and(new LabelAtom("master")); 154 | assertThat(actualAction.getAssignedLabel(task), is(expectedLabel)); 155 | } 156 | 157 | @Test 158 | public void shouldScheduleWhenBuildParamaterGiven() throws Exception { 159 | new Expectations() { 160 | { 161 | task.getBuildWrappersList(); 162 | result = new DescribableList>() { 163 | { 164 | add(bw); 165 | } 166 | }; 167 | 168 | task.getAssignedLabel(); 169 | result = null; 170 | 171 | bw.getAppUsageSettings(); 172 | result = new ArrayList<>(Arrays.asList(new AppUsageSetting("Xcode", "$XCODE_VERSION"))); 173 | } 174 | }; 175 | actions.add(new ParametersAction(new StringParameterValue("XCODE_VERSION", "8.0"))); 176 | 177 | LabelAssignmentAction actualAction = null; 178 | boolean result = handler.shouldSchedule(task, actions); 179 | 180 | for (Action action: actions) { 181 | if (action instanceof LabelAssignmentAction) { 182 | if (! (action instanceof ParametersAction)) { 183 | actualAction = (LabelAssignmentAction) action; 184 | break; 185 | } 186 | } 187 | } 188 | 189 | assertThat(result, is(true)); 190 | assertThat(actualAction, is(notNullValue())); 191 | 192 | Label expectedLabel = new LabelAtom("Xcode-8.0"); 193 | assertThat(actualAction.getAssignedLabel(task), is(expectedLabel)); 194 | } 195 | 196 | @Test 197 | public void shouldScheduleWhenMatrixParamaterGiven() throws Exception { 198 | new Expectations() { 199 | { 200 | matrixTask.getBuildWrappersList(); 201 | result = new DescribableList>() { 202 | { 203 | add(bw); 204 | } 205 | }; 206 | 207 | matrixTask.getAssignedLabel(); 208 | result = null; 209 | 210 | matrixTask.getCombination(); 211 | result = new Combination(new HashMap() { 212 | { 213 | put("XCODE_VERSION", "8.0"); 214 | } 215 | }); 216 | 217 | bw.getAppUsageSettings(); 218 | result = new ArrayList<>(Arrays.asList(new AppUsageSetting("Xcode", "$XCODE_VERSION"))); 219 | } 220 | }; 221 | LabelAssignmentAction actualAction = null; 222 | boolean result = handler.shouldSchedule(matrixTask, actions); 223 | 224 | for (Action action: actions) { 225 | if (action instanceof LabelAssignmentAction) { 226 | actualAction = (LabelAssignmentAction) action; 227 | break; 228 | } 229 | } 230 | 231 | assertThat(result, is(true)); 232 | assertThat(actualAction, is(notNullValue())); 233 | 234 | Label expectedLabel = new LabelAtom("Xcode-8.0"); 235 | assertThat(actualAction.getAssignedLabel(matrixTask), is(expectedLabel)); 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | 6 | org.jenkins-ci.plugins 7 | plugin 8 | 2.7 9 | 10 | 11 | org.jenkins-ci.plugins 12 | app-detector 13 | 1.0.10-SNAPSHOT 14 | hpi 15 | 16 | 17 | 18 | 1.625.3 19 | 20 | 7 21 | 22 | 23 | 2.14 24 | 28 | 29 | 30 | Application Detector Plugin 31 | Detect application versions installed in slave, And enable to execute builds on the specific slave that has an application and the version you want. 32 | https://wiki.jenkins-ci.org/display/JENKINS/Application+Detector+Plugin 33 | 34 | 35 | 36 | 37 | 38 | MIT License 39 | http://opensource.org/licenses/MIT 40 | 41 | 42 | 43 | 44 | 45 | 46 | org.apache.maven.plugins 47 | maven-javadoc-plugin 48 | 49 | -Xdoclint:none 50 | 51 | 52 | 53 | org.codehaus.mojo 54 | cobertura-maven-plugin 55 | 56 | 57 | html 58 | xml 59 | 60 | 61 | 62 | **/*Test.class 63 | **/Messages.class 64 | 65 | 66 | 67 | 68 | package 69 | 70 | cobertura 71 | 72 | 73 | 74 | 75 | 76 | 77 | org.apache.maven.plugins 78 | maven-surefire-plugin 79 | 80 | 81 | InjectedTest.java 82 | 83 | 84 | 85 | 86 | org.codehaus.mojo 87 | cobertura-maven-plugin 88 | 2.7 89 | 90 | 91 | 92 | **/Messages.class 93 | 94 | 95 | 96 | 97 | package 98 | 99 | cobertura 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | org.apache.maven.plugins 110 | maven-checkstyle-plugin 111 | 2.17 112 | 113 | config/google_checks.xml 114 | 115 | 116 | 117 | org.codehaus.mojo 118 | findbugs-maven-plugin 119 | 3.0.5 120 | 121 | 122 | 123 | 124 | 125 | ${project.basedir}/src/test/resources 126 | 127 | 128 | 129 | 130 | 131 | 132 | justice3120 133 | Masayoshi Sakamoto 134 | masayoshi.sakamoto@dena.jp 135 | Asia/Tokyo 136 | 137 | 138 | 139 | 140 | scm:git:git://github.com/jenkinsci/app-detector-plugin.git 141 | scm:git:git@github.com:jenkinsci/app-detector-plugin.git 142 | http://github.com/jenkinsci/app-detector-plugin 143 | app-detector-1.0.1 144 | 145 | 146 | 147 | 148 | repo.jenkins-ci.org 149 | https://repo.jenkins-ci.org/public/ 150 | 151 | 152 | 153 | 154 | repo.jenkins-ci.org 155 | https://repo.jenkins-ci.org/public/ 156 | 157 | 158 | 159 | 160 | 161 | org.zeroturnaround 162 | zt-exec 163 | 1.9 164 | 165 | 166 | org.jenkins-ci.plugins 167 | matrix-project 168 | 1.4 169 | 170 | 171 | org.apache.velocity 172 | velocity 173 | 1.7 174 | 175 | 176 | org.jmockit 177 | jmockit 178 | 1.26 179 | test 180 | 181 | 182 | junit 183 | junit 184 | 4.12 185 | test 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | org.apache.maven.plugins 194 | maven-checkstyle-plugin 195 | 196 | config/google_checks.xml 197 | 198 | 199 | 200 | org.codehaus.mojo 201 | findbugs-maven-plugin 202 | 203 | Max 204 | true 205 | 206 | 207 | 208 | org.codehaus.mojo 209 | cobertura-maven-plugin 210 | 2.7 211 | 212 | 213 | 214 | **/Messages.class 215 | 216 | 217 | 218 | 219 | package 220 | 221 | cobertura 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/appdetector/AppDetectorBuildWrapper.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.appdetector; 2 | 3 | import hudson.Extension; 4 | import hudson.Launcher; 5 | import hudson.model.AbstractBuild; 6 | import hudson.model.AbstractProject; 7 | import hudson.model.BuildListener; 8 | import hudson.model.Computer; 9 | import hudson.model.Node; 10 | import hudson.model.Result; 11 | import hudson.tasks.BuildWrapper; 12 | import hudson.tasks.BuildWrapperDescriptor; 13 | import hudson.util.ComboBoxModel; 14 | import hudson.util.FormValidation; 15 | import hudson.util.ListBoxModel; 16 | import jenkins.model.Jenkins; 17 | import net.sf.json.JSONArray; 18 | import net.sf.json.JSONObject; 19 | import org.jenkinsci.plugins.appdetector.task.AppDetectionTask; 20 | import org.jenkinsci.plugins.appdetector.util.Utils; 21 | import org.kohsuke.stapler.DataBoundConstructor; 22 | import org.kohsuke.stapler.QueryParameter; 23 | import org.kohsuke.stapler.StaplerRequest; 24 | import org.kohsuke.stapler.interceptor.RequirePOST; 25 | 26 | import java.io.PrintStream; 27 | import java.util.ArrayList; 28 | import java.util.List; 29 | import java.util.Map; 30 | 31 | @Extension 32 | public class AppDetectorBuildWrapper extends BuildWrapper { 33 | 34 | private List usageSettings; 35 | 36 | public AppDetectorBuildWrapper() { 37 | super(); 38 | } 39 | 40 | @DataBoundConstructor 41 | public AppDetectorBuildWrapper(List usageSettings) { 42 | this.usageSettings = usageSettings; 43 | } 44 | 45 | @Override 46 | public Environment setUp(AbstractBuild build, final Launcher launcher, BuildListener listener) { 47 | final PrintStream logger = listener.getLogger(); 48 | 49 | final Map buildVars = build.getBuildVariables(); 50 | 51 | Node node = build.getBuiltOn(); 52 | 53 | if (node == null) { 54 | logger.println(Messages.GETTING_NODE_FAILED()); 55 | build.setResult(Result.FAILURE); 56 | return null; 57 | } 58 | 59 | AppLabelSet allLabels = Utils.getApplicationLabels(node); 60 | final List labels = new ArrayList(); 61 | 62 | for (AppUsageSetting setting: usageSettings) { 63 | String expandedVersion = Utils.expandVariables(buildVars, setting.getVersion()); 64 | AppLabelAtom label = allLabels.getApplicationLabel(setting.getAppName(), expandedVersion); 65 | 66 | if (label == null) { 67 | logger.println(Messages.APP_NOT_FOUND(setting.getAppName(), 68 | expandedVersion, node.getNodeName())); 69 | build.setResult(Result.NOT_BUILT); 70 | return null; 71 | } 72 | 73 | labels.add(label); 74 | } 75 | 76 | return new Environment() { 77 | @Override 78 | public void buildEnvVars(Map env) { 79 | Jenkins hudsonInstance = Jenkins.getInstance(); 80 | if (hudsonInstance != null) { 81 | DescriptorImpl descriptor = hudsonInstance.getDescriptorByType(DescriptorImpl.class); 82 | List detectionSettings = descriptor.getDetectionSettings(); 83 | 84 | for (AppLabelAtom label: labels) { 85 | String envVarName = null; 86 | 87 | for (AppDetectionSetting setting: detectionSettings) { 88 | if (label.getApplication().equals(setting.getAppName())) { 89 | envVarName = setting.getHomeDirVarName(); 90 | break; 91 | } 92 | } 93 | 94 | if (envVarName != null && !("".equals(envVarName))) { 95 | env.put(envVarName, label.getHome()); 96 | } 97 | } 98 | } 99 | } 100 | }; 101 | } 102 | 103 | public List getAppUsageSettings() { 104 | return usageSettings; 105 | } 106 | 107 | @Extension 108 | public static final class DescriptorImpl extends BuildWrapperDescriptor { 109 | 110 | private List detectionSettings = new ArrayList(); 111 | 112 | public DescriptorImpl() { 113 | super(AppDetectorBuildWrapper.class); 114 | load(); 115 | } 116 | 117 | @Override 118 | public String getDisplayName() { 119 | return Messages.JOB_DESCRIPTION(); 120 | } 121 | 122 | @Override 123 | public boolean configure(StaplerRequest req, JSONObject json) throws FormException { 124 | detectionSettings = new ArrayList(); 125 | 126 | JSONArray settingArray = json.optJSONArray("setting"); 127 | if (settingArray != null) { 128 | for (Object settingObj: settingArray) { 129 | JSONObject setting = JSONObject.fromObject(settingObj); 130 | detectionSettings 131 | .add(new AppDetectionSetting( 132 | setting.getString("appName"), 133 | setting.getString("script"), 134 | setting.getBoolean("detectOnLinux"), 135 | setting.getBoolean("detectOnOsx"), 136 | setting.getBoolean("detectOnWindows"), 137 | setting.getString("homeDirVarName") 138 | )); 139 | } 140 | } else { 141 | JSONObject setting = json.optJSONObject("setting"); 142 | if (setting != null) { 143 | detectionSettings 144 | .add(new AppDetectionSetting( 145 | setting.getString("appName"), 146 | setting.getString("script"), 147 | setting.getBoolean("detectOnLinux"), 148 | setting.getBoolean("detectOnOsx"), 149 | setting.getBoolean("detectOnWindows"), 150 | setting.getString("homeDirVarName") 151 | )); 152 | } 153 | } 154 | 155 | save(); 156 | return true; 157 | } 158 | 159 | @Override 160 | public BuildWrapper newInstance(StaplerRequest req, JSONObject json) throws FormException { 161 | List usageSettings = new ArrayList(); 162 | 163 | JSONArray settingArray = json.optJSONArray("setting"); 164 | if (settingArray != null) { 165 | for (Object settingObj: settingArray) { 166 | JSONObject setting = JSONObject.fromObject(settingObj); 167 | usageSettings 168 | .add(new AppUsageSetting( 169 | setting.getString("appName"), 170 | setting.getString("appVersion") 171 | )); 172 | } 173 | } else { 174 | JSONObject setting = json.optJSONObject("setting"); 175 | if (setting != null) { 176 | usageSettings 177 | .add(new AppUsageSetting( 178 | setting.getString("appName"), 179 | setting.getString("appVersion") 180 | )); 181 | } 182 | } 183 | 184 | return new AppDetectorBuildWrapper(usageSettings); 185 | } 186 | 187 | @Override 188 | public boolean isApplicable(AbstractProject item) { 189 | return true; 190 | } 191 | 192 | /** 193 | * Fill in the value of the select element of the application name in Jenkins' Web view. 194 | * @return List of application names. 195 | */ 196 | public ListBoxModel doFillAppNameItems() { 197 | ListBoxModel items = new ListBoxModel(); 198 | AppLabelSet labels = Utils.getApplicationLabels(); 199 | for (String appName: labels.getAppNames()) { 200 | items.add(appName); 201 | } 202 | return items; 203 | } 204 | 205 | public ComboBoxModel doFillAppVersionItems(@QueryParameter("appName") final String appName) { 206 | AppLabelSet labels = Utils.getApplicationLabels(); 207 | return new ComboBoxModel(labels.getSortedAppVersions(appName)); 208 | } 209 | 210 | /** 211 | * Fill in the value of select element of node name in Jenkins' Web view. 212 | * @return List of Jenkins node names. 213 | */ 214 | public ListBoxModel doFillNodeItems() { 215 | ListBoxModel items = new ListBoxModel(); 216 | 217 | Computer[] allComputers = Utils.getAllComputers(); 218 | for (Computer computer: allComputers) { 219 | if (computer.isOnline()) { 220 | items.add(computer.getDisplayName(), computer.getName()); 221 | } else { 222 | items.add(computer.getDisplayName() + "(offline)", computer.getName()); 223 | } 224 | } 225 | return items; 226 | } 227 | 228 | /** 229 | * Test whether Groovy script for application detection is valid. 230 | * This method is called from Jenkins' Web View. 231 | * @param script Groovy script to be tested. 232 | * @param node Jenkins node name to execute script. 233 | * @param onLinux Whether to perform detection on Linux. 234 | * @param onOsx Whether to perform detection on Mac. 235 | * @param onWindows Whether to perform detection on Windows. 236 | * @return test results of the script. 237 | */ 238 | @RequirePOST 239 | public FormValidation doTestScript( 240 | @QueryParameter("script") final String script, 241 | @QueryParameter("node") final String node, 242 | @QueryParameter("detectOnLinux") final boolean onLinux, 243 | @QueryParameter("detectOnOsx") final boolean onOsx, 244 | @QueryParameter("detectOnWindows") final boolean onWindows) { 245 | 246 | Jenkins jenkins = Jenkins.getInstance(); 247 | if (jenkins == null) { 248 | return FormValidation.error(Messages.CANNOT_GET_HUDSON_INSTANCE()); 249 | } 250 | 251 | jenkins.checkPermission(Jenkins.RUN_SCRIPTS); 252 | AppDetectionSetting setting = 253 | new AppDetectionSetting("Test", script, onLinux, onOsx, onWindows, "TEST"); 254 | 255 | try { 256 | Computer computer = jenkins.getComputer(node); 257 | String result = computer.getChannel().call(new AppDetectionTask(setting)); 258 | return FormValidation.ok(result); 259 | } catch (Exception e) { 260 | return FormValidation.error(e, e.getMessage()); 261 | } 262 | } 263 | 264 | public List getDetectionSettings() { 265 | return detectionSettings; 266 | } 267 | } 268 | } 269 | -------------------------------------------------------------------------------- /config/google_checks.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 67 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 92 | 93 | 94 | 96 | 97 | 98 | 99 | 101 | 102 | 103 | 104 | 106 | 107 | 112 | 113 | 114 | 115 | 116 | 118 | 119 | 120 | 121 | 123 | 124 | 125 | 126 | 128 | 129 | 130 | 131 | 133 | 134 | 135 | 136 | 138 | 140 | 142 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | --------------------------------------------------------------------------------