├── .gitignore ├── .mvn ├── extensions.xml └── maven.config ├── CHANGELOG.md ├── Jenkinsfile ├── LICENSE ├── README.md ├── README_JobConfiguration.md ├── README_PipelineConfiguration.md ├── README_SystemConfiguration.md ├── pom.xml ├── screenshots ├── 1-system-settings.png ├── 2-build-configuration-1.png ├── 3-build-configuration-2.png ├── 3-build-configuration-2b.png ├── pipelineSyntaxGenerator.png └── pipelineSyntaxGenerator2.png └── src ├── main ├── java │ └── org │ │ └── jenkinsci │ │ └── plugins │ │ └── ParameterizedRemoteTrigger │ │ ├── Auth.java │ │ ├── BasicBuildContext.java │ │ ├── BuildContext.java │ │ ├── ConnectionResponse.java │ │ ├── JenkinsCrumb.java │ │ ├── RemoteBuildConfiguration.java │ │ ├── RemoteJenkinsServer.java │ │ ├── auth2 │ │ ├── Auth2.java │ │ ├── BearerTokenAuth.java │ │ ├── CredentialsAuth.java │ │ ├── NoneAuth.java │ │ ├── NullAuth.java │ │ └── TokenAuth.java │ │ ├── exceptions │ │ ├── CredentialsNotFoundException.java │ │ ├── ExceedRetryLimitException.java │ │ ├── ForbiddenException.java │ │ ├── UnauthorizedException.java │ │ └── UrlNotFoundException.java │ │ ├── parameters2 │ │ ├── FileParameters.java │ │ ├── JobParameters.java │ │ ├── MapParameter.java │ │ ├── MapParameters.java │ │ └── StringParameters.java │ │ ├── pipeline │ │ ├── Handle.java │ │ ├── PrintStreamWrapper.java │ │ └── RemoteBuildPipelineStep.java │ │ ├── remoteJob │ │ ├── QueueItem.java │ │ ├── QueueItemData.java │ │ ├── QueueItemStatus.java │ │ ├── RemoteBuildInfo.java │ │ ├── RemoteBuildInfoExporterAction.java │ │ └── RemoteBuildStatus.java │ │ └── utils │ │ ├── Base64Utils.java │ │ ├── DropCachePeriodicWork.java │ │ ├── FormValidationUtils.java │ │ ├── HttpHelper.java │ │ ├── NaiveTrustManager.java │ │ ├── OtelUtils.java │ │ ├── RestUtils.java │ │ ├── StringTools.java │ │ └── TokenMacroUtils.java └── resources │ ├── index.jelly │ └── org │ └── jenkinsci │ └── plugins │ └── ParameterizedRemoteTrigger │ ├── Auth │ └── config.jelly │ ├── RemoteBuildConfiguration │ ├── config.jelly │ ├── global.jelly │ ├── help-auth2.html │ ├── help-disabled.html │ ├── help-enhancedLogging.html │ ├── help-job.html │ ├── help-maxConn.html │ ├── help-parameters2.html │ ├── help-remoteJenkinsUrl.html │ ├── help-shouldNotFailBuild.html │ ├── help-token.html │ ├── help-trustAllCertificates.html │ ├── help-useCrumbCache.html │ └── help-useJobInfoCache.html │ ├── RemoteJenkinsServer │ ├── config.jelly │ ├── help-address.html │ ├── help-apiToken.html │ ├── help-auth2.html │ ├── help-displayName.html │ ├── help-hasBuildTokenRootSupport.html │ ├── help-trustAllCertificates.html │ ├── help-username.html │ └── help.html │ ├── auth2 │ ├── BearerTokenAuth │ │ └── config.jelly │ ├── CredentialsAuth │ │ └── config.jelly │ └── TokenAuth │ │ └── config.jelly │ ├── parameters2 │ ├── FileParameters │ │ └── config.jelly │ ├── MapParameter │ │ └── config.jelly │ ├── MapParameters │ │ └── config.jelly │ └── StringParameters │ │ └── config.jelly │ └── pipeline │ └── RemoteBuildPipelineStep │ ├── config.jelly │ ├── help-auth.html │ ├── help-blockBuildUntilComplete.html │ ├── help-disabled.html │ ├── help-enhancedLogging.html │ ├── help-job.html │ ├── help-maxConn.html │ ├── help-parameters.html │ ├── help-pollInterval.html │ ├── help-preventRemoteBuildQueue.html │ ├── help-remoteJenkinsName.html │ ├── help-remoteJenkinsUrl.html │ ├── help-shouldNotFailBuild.html │ ├── help-token.html │ ├── help-trustAllCertificates.html │ ├── help-useCrumbCache.html │ ├── help-useJobInfoCache.html │ └── help.html └── test └── java └── org └── jenkinsci └── plugins └── ParameterizedRemoteTrigger ├── OpenTelemeterTest.java ├── RemoteBuildConfigurationTest.java ├── RemoteJenkinsServerTest.java ├── TestConst.java ├── auth2 └── Auth2Test.java ├── pipeline └── HandleTest.java ├── remoteJob ├── BuildInfoExporterActionTest.java ├── BuildInfoTest.java └── QueueItemTest.java └── utils ├── Base64UtilsTest.java └── FormValidationUtilsTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | 3 | # Package Files # 4 | *.jar 5 | *.war 6 | *.ear 7 | 8 | # Eclipse-specific stuff 9 | .classpath 10 | .project 11 | *.prefs 12 | 13 | # Idea-specific stuff 14 | .idea/ 15 | *.iml 16 | 17 | # jenkins data directory, build dir and release settings 18 | work 19 | release.properties 20 | target 21 | *.releaseBackup 22 | *.swp 23 | -------------------------------------------------------------------------------- /.mvn/extensions.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | io.jenkins.tools.incrementals 4 | git-changelist-maven-extension 5 | 1.7 6 | 7 | 8 | -------------------------------------------------------------------------------- /.mvn/maven.config: -------------------------------------------------------------------------------- 1 | -Pconsume-incrementals 2 | -Pmight-produce-incrementals 3 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | /* 2 | See the documentation for more options: 3 | https://github.com/jenkins-infra/pipeline-library/ 4 | */ 5 | buildPlugin(useContainerAgent: true, configurations: [ 6 | [platform: 'linux', jdk: 21], 7 | [platform: 'windows', jdk: 17], 8 | ]) 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Maurice Williams 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Parameterized-Remote-Trigger-Plugin 2 | =================================== 3 | 4 | A plugin for Jenkins CI that gives you the ability to trigger parameterized builds on a **remote** Jenkins server as part of your build. 5 | 6 | Similar to the [Parameterized Trigger Plugin](https://wiki.jenkins-ci.org/display/JENKINS/Parameterized+Trigger+Plugin), but for remote servers. 7 | 8 | This is done by calling the ```/buildWithParameters``` URL on the remote server. (or the ```/build``` URL, if you don't specify any parameters) 9 | 10 | This plugin also has support for build authorization tokens (as defined [here](https://wiki.jenkins-ci.org/display/JENKINS/Quick+and+Simple+Security) ), and plays nicely with these other guys: 11 | - [Build Token Root Plugin](https://wiki.jenkins-ci.org/display/JENKINS/Build+Token+Root+Plugin) 12 | - [Credentials Plugin](https://wiki.jenkins-ci.org/display/JENKINS/Credentials+Plugin) 13 | - [Token Macro Plugin](https://wiki.jenkins-ci.org/display/JENKINS/Token+Macro+Plugin) 14 | 15 | Please take a look at the [change log](CHANGELOG.md) for a complete list of features and what not. 16 | 17 | ## Usage 18 | 1. [System configuration options](README_SystemConfiguration.md)
19 | 2. [Job setup options](README_JobConfiguration.md)
20 | 3. [Pipeline setup options](README_PipelineConfiguration.md) 21 | -------------------------------------------------------------------------------- /README_JobConfiguration.md: -------------------------------------------------------------------------------- 1 | # Job setup options 2 | 3 | Select `Build` > `Add build step` > `Trigger a remote parameterized job` 4 | 5 | ![select from drop-down](screenshots/2-build-configuration-1.png) 6 | 7 | You can select a globally configured remote server and only specify a job name here. 8 | The full URL is calculated based on the remote server, the authentication is taken from the global configuration. 9 | However it is possible to override the Jenkins base URL (or set the full Job URL) and override credentials used for authentication. 10 | 11 | ![Job setup options](screenshots/3-build-configuration-2.png) 12 | 13 | You can also specify the full job URL and use only the authentication from the global configuration or specify the authentication per job. 14 | 15 | ![Job setup options](screenshots/3-build-configuration-2b.png) 16 | 17 | 18 | # Support of Folders on Remote Jenkins 19 | [See here for more information](README_PipelineConfiguration.md#user-content-folders) 20 | -------------------------------------------------------------------------------- /README_PipelineConfiguration.md: -------------------------------------------------------------------------------- 1 | # Pipeline setup options 2 | 3 | - [Defaults](#user-content-defaults) 4 | - [Remote Server Configuration](#user-content-server) 5 | - [Authentication](#user-content-authentication) 6 | - [The Handle Object](#user-content-handle) 7 | - [Blocking vs. Non-Blocking](#user-content-blockingnonblocking) 8 | - [Blocking usage (recommended)](#user-content-blocking) 9 | - [Non-blocking usage](#user-content-nonblocking) 10 | - [Support of Folders on Remote Jenkins](#user-content-folders) 11 | 12 | The `triggerRemoteJob` pipeline step triggers a job on a remote Jenkins. This command is also available in the Jenkins Pipeline Syntax Generator: 13 | 14 | You can select a globally configured remote server and only specify a job name here. 15 | The full URL is calculated based on the remote server, the authentication is taken from the global configuration. 16 | However it is possible to override the Jenkins base URL (or set the full Job URL) and override credentials used for authentication. 17 | 18 | ![Pipeline Syntax Generator](screenshots/pipelineSyntaxGenerator.png) 19 | 20 | You can also specify the full job URL and use only the authentication from the global configuration or specify the authentication per job. 21 | 22 | ![Pipeline Syntax Generator](screenshots/pipelineSyntaxGenerator2.png) 23 | 24 | 25 |
26 | 27 | ## Defaults 28 | The simplest way to trigger a job is: 29 | ``` 30 | def handle = triggerRemoteJob job: 'https://myjenkins:8080/job/JobWithoutParams' 31 | echo 'Remote Status: ' + handle.getBuildStatus().toString() 32 | ``` 33 | 34 | If the job has parameters: 35 | ``` 36 | def handle = triggerRemoteJob job: 'https://myjenkins:8080/job/JobWithParams', parameters: 'param1=abc\nparam2=xyz' 37 | ``` 38 | 39 | If authentication is required: 40 | ``` 41 | def handle = triggerRemoteJob job: 'https://myjenkins:8080/job/JobWithoutParams', auth: TokenAuth(apiToken: '', userName: '') 42 | ``` 43 | 44 | 45 | The pipeline will wait/block until the remote build finished. 46 | 47 | 48 |
49 | 50 | ## Remote Server Configuration 51 | 52 | :information_source: You can configure jobs/pipelines also without any global configuration. 53 | 54 | The remote Jenkins server containing the target job(s) can be configured in different ways. 55 | - **Jenkins System Configuration**
56 | Remote servers can be configured in the [Jenkins System Configuration](README_SystemConfiguration.md) and referenced in Pipelines by their name. The server configuration can also include authentication settings.
57 | `triggerRemoteJob remoteJenkinsName: 'remoteJenkins' ...` 58 | - **Override Server URL**
59 | On Pipeline level the URL can be set/overridden with parameter `remoteJenkinsUrl`.
60 | `triggerRemoteJob remoteJenkinsUrl: 'https://myjenkins:8080' ...`
61 | If combined with `remoteJenkinsName` only the URL of the globally configured server will be overridden, the other settings like authentication will be used from the global configuration.
62 | `triggerRemoteJob remoteJenkinsName: 'remoteJenkins', remoteJenkinsUrl: 'https://myjenkins:8080' ...`
63 | - **Full Job URL**
64 | It is also possible to configure the full job URL instead of the job name only and the remote Jenkins server root URL.
65 | `triggerRemoteJob job: 'https://myjenkins:8080/job/MyJob' ...`
66 | 67 | :information_source: If the remote Jenkins uses folders please [read this](#user-content-folders). 68 | 69 |
70 | 71 | ## Authentication 72 | Authentication can be configured globally in the system configuration or set/overridden for each pipeline via the `auth` parameter. 73 | 74 | The following authentication types are available: 75 | - **Token Authentication** The specified user id and Jenkins API token is used.
76 | ```auth: TokenAuth(apiToken: '', userName: '')``` 77 | - **Bearer Token Authentication** The specified token is inserted to a "Authentication: Bearer" header in REST API requests.
78 | This is useful when the Jenkins deployment is fronted by a token authentication mechanism (such as when running on Red Hat OpenShift)
79 | ```auth: BearerTokenAuth(token: '')``` 80 | - **Credentials Authentication** The specified Jenkins Credentials are used. This can be either user/password or user/API Token.
81 | ```auth: CredentialsAuth(credentials: '')``` 82 | - **No Authentication** No Authorization header will be sent, independent of the global 'remote host' settings.
83 | ```auth: NoneAuth()``` 84 | 85 | **Note:** *Jenkins API Tokens* are recommended since, if stolen, they allow access only to a specific Jenkins 86 | while user and password typically provide access to many systems. 87 | 88 | 89 | 90 |
91 | 92 | ## The Handle Object 93 | The `Handle` object provides the following methods: 94 | 95 | - `String getJobName()` returns the remote job name 96 | - `URL getBuildUrl()` returns the remote build URL including the build number 97 | - `int getBuildNumber()` returns the remote build number 98 | - `RemoteBuildInfo getBuildInfo()` return information regarding the current remote build 99 | - `RemoteBuildStatus getBuildStatus()` returns the current remote build status 100 | - `Result getBuildResult()` return the result of the remote build 101 | - `RemoteBuildStatus updateBuildStatusBlocking()` waits for completion and returns the build result 102 | - `RemoteBuildStatus updateBuildStatus()` update the handles build status. Required for getBuildInfo() etc. to yield updated results. 103 | - `boolean isFinished()` true if the remote build finished 104 | - `boolean isQueued()` true if the job is queued but not yet running 105 | - `String toString()` 106 | - `Object readJsonFileFromBuildArchive(String filename)`
107 | This is a convenience method to download and parse the specified JSON file (filename or relative path) from the build archive. 108 | This mechanism might be used by remote builds to provide return parameters. 109 | 110 | ``` 111 | def handle = triggerRemoteJob blockBuildUntilComplete: true, ... 112 | def results = handle.readJsonFileFromBuildArchive('build-results.json') 113 | echo results.urlToTestResults //just an example 114 | ``` 115 | 116 | - Enum of RemoteBuildStatus may have the values: `UNKNOWN`, `NOT_STARTED`, `QUEUED`, `RUNNING`, if the remote job did not finish yet. 117 | - Enum of Result may have the values: `ABORTED`, `FAILURE`, `NOT_BUILT`, `SUCCESS`, `UNSTABLE`, if the remote job finished the status reflects the Jenkins build `Result`. 118 | 119 | 120 |
121 | 122 | ## Blocking vs. Non-Blocking 123 | The `triggerRemoteJob` command always returns a [`Handle`](#user-content-the-handle-object) object. This object can be used to track the status of the remote build (instead of using the environment variables like in the Job case). 124 | 125 | There are two ways to use the command, in a blocking way (it will wait/block until the remote job finished) and in a non-blocking way (the handle is returned immediately and the remote status can be checked asynchronously). 126 | 127 |
128 | 129 | ### Blocking usage (recommended) 130 | The recommended way to trigger jobs is in a blocking way. Set `blockBuildUntilComplete: true` to let the plugin wait 131 | until the remote build finished: 132 | ``` 133 | def handle = triggerRemoteJob( 134 | remoteJenkinsName: 'remoteJenkins', 135 | job: 'TheJob', 136 | parameters: 'a=b', 137 | blockBuildUntilComplete: true, 138 | ...) 139 | echo 'Remote Status: ' + handle.getBuildStatus().toString() 140 | ``` 141 | 142 |
143 | 144 | ### Non-blocking usage 145 | It is also possible to use it in a non-blocking way. Set `blockBuildUntilComplete: false` and the plugin directly 146 | returns the `handle` for further tracking the status: 147 | ``` 148 | def handle = triggerRemoteJob( 149 | remoteJenkinsName: 'remoteJenkins', 150 | job: 'TheJob', 151 | parameters: 'a=b', 152 | blockBuildUntilComplete: false, 153 | ...) 154 | while( !handle.isFinished() ) { 155 | echo 'Current Status: ' + handle.getBuildStatus().toString(); 156 | sleep 5 157 | handle.updateBuildStatus() 158 | } 159 | echo handle.getBuildStatus().toString(); 160 | ``` 161 | 162 | Even with `blockBuildUntilComplete: false` it is possible to wait synchronously until the remote job finished: 163 | ``` 164 | def handle = triggerRemoteJob blockBuildUntilComplete: false, ... 165 | def status = handle.updateBuildStatusBlocking() 166 | ``` 167 | 168 | :warning: Currently the plugin cannot log to the pipeline log directly if used in non-blocking mode. As workaround you can use `handle.lastLog()` after each command to get the log entries. 169 | 170 | 171 |
172 | 173 | # Support of Folders on Remote Jenkins 174 | 175 | The Parameterized Remote Trigger plugin also supports the use of folders on the remote Jenkins server, for example if it uses the [`CloudBees Folders Plugin`](https://wiki.jenkins.io/display/JENKINS/CloudBees+Folders+Plugin) or the [`GitHub Branch Source Plugin`](https://plugins.jenkins.io/github-branch-source) (formerly [`GitHub Organization Folder Plugin`](https://wiki.jenkins.io/display/JENKINS/GitHub+Organization+Folder+Plugin)) 176 | 177 | Remote URLs with folders look like this 178 | ``` 179 | https://server:8080/job/Folder1/job/Folder2/job/TheJob 180 | ``` 181 | 182 | Without folders it would only be `https://server:8080/job/TheJob` 183 | 184 | To be able to trigger such jobs you have to either 185 | 1. Specify the full Job URL as `Remote Job Name or URL` 186 | 2. Specify the job fullname as `Remote Job Name or URL` + a globally configured [`Remote Host`](#user-content-server).
187 | The jobs fullname in the example above would be 'Folder1/Folder2/TheJob'. 188 | 189 | 190 |






























191 | -------------------------------------------------------------------------------- /README_SystemConfiguration.md: -------------------------------------------------------------------------------- 1 | # System configuration options 2 | 3 | The Parameterized Remote Trigger plugin can used without any global Jenkins system configuration. 4 | 5 | However it might be useful to configure one or more remote server & credentials globally. 6 | From a job/pipeline this server can be referenced together with the job name, without the need to specify the full URL and authentication credentials. Still it is possible to specify or override each part on job/pipeline level. 7 | 8 | ![System onfiguration option](screenshots/1-system-settings.png) 9 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4.0.0 4 | 5 | org.jenkins-ci.plugins 6 | plugin 7 | 5.8 8 | 9 | 10 | 11 | 12 | 3.2.2 13 | -SNAPSHOT 14 | 15 | 2.479 16 | ${jenkins.baseline}.1 17 | jenkinsci/parameterized-remote-trigger-plugin 18 | 19 | 3.1.6 20 | 21 | 22 | Parameterized-Remote-Trigger 23 | ${revision}${changelist} 24 | hpi 25 | Parameterized Remote Trigger Plugin 26 | This plugin gives you the ability to trigger parameterized builds on a remote Jenkins server as part of your build. 27 | https://github.com/jenkinsci/parameterized-remote-trigger-plugin 28 | 29 | 30 | 31 | MIT license 32 | All source code is under the MIT license. 33 | 34 | 35 | 36 | 37 | 38 | cashlalala 39 | KaiHsiang Chang 40 | 41 | 42 | 43 | 44 | 45 | 54 | 55 | 56 | 57 | 58 | scm:git:https://github.com/${gitHubRepo}.git 59 | scm:git:git@github.com:${gitHubRepo}.git 60 | https://github.com/${gitHubRepo} 61 | ${scmTag} 62 | 63 | 64 | 65 | 66 | repo.jenkins-ci.org 67 | https://repo.jenkins-ci.org/public/ 68 | 69 | 70 | 71 | 72 | 73 | repo.jenkins-ci.org 74 | https://repo.jenkins-ci.org/public/ 75 | 76 | 77 | 78 | 79 | 80 | 81 | io.jenkins.tools.bom 82 | bom-${jenkins.baseline}.x 83 | 3944.v1a_e4f8b_452db_ 84 | import 85 | pom 86 | 87 | 88 | 89 | 90 | 91 | org.jenkins-ci.plugins 92 | credentials 93 | 94 | 95 | org.jenkins-ci.plugins 96 | token-macro 97 | 98 | 99 | org.jenkins-ci.plugins 100 | script-security 101 | true 102 | 103 | 104 | org.jenkins-ci.plugins.workflow 105 | workflow-step-api 106 | true 107 | 108 | 109 | io.jenkins.plugins 110 | opentelemetry 111 | 2.16.0 112 | true 113 | 114 | 115 | org.mockito 116 | mockito-core 117 | test 118 | 119 | 120 | org.mock-server 121 | mockserver-junit-rule 122 | 5.15.0 123 | test 124 | 125 | 126 | 127 | javax.servlet 128 | javax.servlet-api 129 | 130 | 131 | 132 | 133 | 134 | 135 | -------------------------------------------------------------------------------- /screenshots/1-system-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/parameterized-remote-trigger-plugin/e783257733e607866fe00bf0258427c79da92584/screenshots/1-system-settings.png -------------------------------------------------------------------------------- /screenshots/2-build-configuration-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/parameterized-remote-trigger-plugin/e783257733e607866fe00bf0258427c79da92584/screenshots/2-build-configuration-1.png -------------------------------------------------------------------------------- /screenshots/3-build-configuration-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/parameterized-remote-trigger-plugin/e783257733e607866fe00bf0258427c79da92584/screenshots/3-build-configuration-2.png -------------------------------------------------------------------------------- /screenshots/3-build-configuration-2b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/parameterized-remote-trigger-plugin/e783257733e607866fe00bf0258427c79da92584/screenshots/3-build-configuration-2b.png -------------------------------------------------------------------------------- /screenshots/pipelineSyntaxGenerator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/parameterized-remote-trigger-plugin/e783257733e607866fe00bf0258427c79da92584/screenshots/pipelineSyntaxGenerator.png -------------------------------------------------------------------------------- /screenshots/pipelineSyntaxGenerator2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jenkinsci/parameterized-remote-trigger-plugin/e783257733e607866fe00bf0258427c79da92584/screenshots/pipelineSyntaxGenerator2.png -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/ParameterizedRemoteTrigger/Auth.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.ParameterizedRemoteTrigger; 2 | 3 | import java.io.Serializable; 4 | import java.util.ArrayList; 5 | import java.util.Collections; 6 | import java.util.List; 7 | 8 | import org.jenkinsci.plugins.ParameterizedRemoteTrigger.auth2.Auth2; 9 | import org.jenkinsci.plugins.ParameterizedRemoteTrigger.auth2.CredentialsAuth; 10 | import org.jenkinsci.plugins.ParameterizedRemoteTrigger.auth2.NoneAuth; 11 | import org.jenkinsci.plugins.ParameterizedRemoteTrigger.auth2.NullAuth; 12 | import org.jenkinsci.plugins.ParameterizedRemoteTrigger.auth2.TokenAuth; 13 | import org.jenkinsci.plugins.ParameterizedRemoteTrigger.exceptions.CredentialsNotFoundException; 14 | import org.kohsuke.stapler.DataBoundConstructor; 15 | import org.kohsuke.stapler.Stapler; 16 | 17 | import com.cloudbees.plugins.credentials.CredentialsProvider; 18 | import com.cloudbees.plugins.credentials.common.StandardUsernameCredentials; 19 | import com.cloudbees.plugins.credentials.common.StandardUsernameListBoxModel; 20 | import com.cloudbees.plugins.credentials.common.UsernamePasswordCredentials; 21 | import com.cloudbees.plugins.credentials.domains.DomainRequirement; 22 | 23 | import hudson.Extension; 24 | import hudson.model.AbstractDescribableImpl; 25 | import hudson.model.Descriptor; 26 | import hudson.model.Item; 27 | import hudson.security.ACL; 28 | import hudson.util.ListBoxModel; 29 | import hudson.util.Secret; 30 | 31 | /** 32 | * We need to keep this for compatibility - old config deserialization! 33 | * @deprecated since 2.3.0-SNAPSHOT - use {@link Auth2} instead. 34 | */ 35 | public class Auth extends AbstractDescribableImpl implements Serializable { 36 | 37 | private static final long serialVersionUID = 5110932168554914718L; 38 | 39 | public static final String NONE = "none"; 40 | public static final String API_TOKEN = "apiToken"; 41 | public static final String CREDENTIALS_PLUGIN = "credentialsPlugin"; 42 | 43 | private final String authType; 44 | private final String username; 45 | private final String apiToken; 46 | private final String creds; 47 | 48 | @DataBoundConstructor 49 | public Auth(String authType, String username, String apiToken, String creds) { 50 | this.authType = authType; 51 | this.username = username; 52 | this.apiToken = apiToken; 53 | this.creds = creds; 54 | } 55 | 56 | public String getAuthType() { 57 | return authType; 58 | } 59 | 60 | public String getUsername() { 61 | return username; 62 | } 63 | 64 | public String getApiToken() { 65 | return apiToken; 66 | } 67 | 68 | public String getCreds() { 69 | return creds; 70 | } 71 | 72 | public Boolean isMatch(String value) { 73 | return authType.equals(value); 74 | } 75 | 76 | public String getUser(){ 77 | if (authType.equals(API_TOKEN)){ 78 | return username; 79 | } else if (authType.equals(CREDENTIALS_PLUGIN)){ 80 | UsernamePasswordCredentials creds = getCredentials(); 81 | return creds != null ? creds.getUsername() : ""; 82 | } else { 83 | return ""; 84 | } 85 | } 86 | 87 | public String getPassword(){ 88 | if (authType.equals(API_TOKEN)){ 89 | return apiToken; 90 | } else if (authType.equals(CREDENTIALS_PLUGIN)){ 91 | UsernamePasswordCredentials creds = getCredentials(); 92 | return creds != null ? creds.getPassword().getPlainText() : ""; 93 | } else { 94 | return ""; 95 | } 96 | } 97 | 98 | /** 99 | * Looks up the credentialsID attached to this object in the Global Credentials plugin datastore 100 | * @return the matched credentials 101 | */ 102 | private UsernamePasswordCredentials getCredentials() { 103 | Item item = null; 104 | 105 | List listOfCredentials = CredentialsProvider.lookupCredentials( 106 | StandardUsernameCredentials.class, item, ACL.SYSTEM, Collections. emptyList()); 107 | 108 | return (UsernamePasswordCredentials) findCredential(creds, listOfCredentials); 109 | } 110 | 111 | private StandardUsernameCredentials findCredential(String credetialId, List listOfCredentials){ 112 | for (StandardUsernameCredentials cred : listOfCredentials) { 113 | if (credetialId.equals(cred.getId())) { 114 | return cred; 115 | } 116 | } 117 | return null; 118 | } 119 | 120 | @Override 121 | public DescriptorImpl getDescriptor() { 122 | return (DescriptorImpl) super.getDescriptor(); 123 | } 124 | 125 | /** 126 | * We need to keep this for compatibility - old config deserialization! 127 | * @deprecated since 2.3.0-SNAPSHOT - use {@link Auth2} instead. 128 | */ 129 | @Extension 130 | public static class DescriptorImpl extends Descriptor { 131 | @Override 132 | public String getDisplayName() { 133 | return ""; 134 | } 135 | 136 | public static ListBoxModel doFillCredsItems() { 137 | StandardUsernameListBoxModel model = new StandardUsernameListBoxModel(); 138 | 139 | Item item = Stapler.getCurrentRequest2().findAncestorObject(Item.class); 140 | 141 | List listOfAllCredentails = CredentialsProvider.lookupCredentials( 142 | StandardUsernameCredentials.class, item, ACL.SYSTEM, Collections. emptyList()); 143 | 144 | List listOfSandardUsernameCredentials = new ArrayList(); 145 | 146 | // since we only care about 'UsernamePasswordCredentials' objects, lets seek those out and ignore the rest. 147 | for (StandardUsernameCredentials c : listOfAllCredentails) { 148 | if (c instanceof UsernamePasswordCredentials) { 149 | listOfSandardUsernameCredentials.add(c); 150 | } 151 | } 152 | model.withAll(listOfSandardUsernameCredentials); 153 | 154 | return model; 155 | } 156 | } 157 | 158 | public static Auth auth2ToAuth(Auth2 auth) { 159 | if (auth == null) 160 | return null; 161 | if (auth instanceof NoneAuth) { 162 | return new Auth(Auth.NONE, null, null, null); 163 | } else if (auth instanceof TokenAuth) { 164 | TokenAuth tokenAuth = (TokenAuth) auth; 165 | return new Auth(Auth.API_TOKEN, tokenAuth.getUserName(), tokenAuth.getApiToken().getPlainText(), null); 166 | } else if (auth instanceof CredentialsAuth) { 167 | CredentialsAuth credAuth = (CredentialsAuth) auth; 168 | try { 169 | String credUser = credAuth.getUserName(null); 170 | String credPass = credAuth.getPassword(null); 171 | return new Auth(Auth.CREDENTIALS_PLUGIN, credUser, credPass, credAuth.getCredentials()); 172 | } 173 | catch (CredentialsNotFoundException e) { 174 | return new Auth(Auth.CREDENTIALS_PLUGIN, "", "", credAuth.getCredentials()); 175 | } 176 | } else { 177 | return null; 178 | } 179 | } 180 | 181 | public static Auth2 authToAuth2(List oldAuth) { 182 | if(oldAuth == null || oldAuth.size() <= 0) return NullAuth.INSTANCE; 183 | return authToAuth2(oldAuth.get(0)); 184 | } 185 | 186 | public static Auth2 authToAuth2(Auth oldAuth) { 187 | String authType = oldAuth.getAuthType(); 188 | if (Auth.NONE.equals(authType)) { 189 | return NoneAuth.INSTANCE; 190 | } else if (Auth.API_TOKEN.equals(authType)) { 191 | TokenAuth newAuth = new TokenAuth(); 192 | newAuth.setUserName(oldAuth.getUsername()); 193 | newAuth.setApiToken(Secret.fromString(oldAuth.getApiToken())); 194 | return newAuth; 195 | } else if (Auth.CREDENTIALS_PLUGIN.equals(authType)) { 196 | CredentialsAuth newAuth = new CredentialsAuth(); 197 | newAuth.setCredentials(oldAuth.getCreds()); 198 | return newAuth; 199 | } else { 200 | return NullAuth.INSTANCE; 201 | } 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/ParameterizedRemoteTrigger/BasicBuildContext.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.ParameterizedRemoteTrigger; 2 | 3 | import edu.umd.cs.findbugs.annotations.CheckForNull; 4 | import edu.umd.cs.findbugs.annotations.Nullable; 5 | 6 | import hudson.FilePath; 7 | import hudson.model.Run; 8 | import hudson.model.TaskListener; 9 | 10 | /** 11 | * This object wraps a {@link Run}, {@link FilePath}, and {@link TaskListener} - 12 | * the typical objects passed from one method to the other in a Jenkins Builder/BuildStep implementation.
13 | *
14 | * The reason for wrapping is simplicity. 15 | */ 16 | public class BasicBuildContext 17 | { 18 | @Nullable @CheckForNull 19 | public final Run run; 20 | 21 | @Nullable @CheckForNull 22 | public final FilePath workspace; 23 | 24 | @Nullable @CheckForNull 25 | public final TaskListener listener; 26 | 27 | 28 | public BasicBuildContext(@Nullable Run run, @Nullable FilePath workspace, @Nullable TaskListener listener) { 29 | this.run = run; 30 | this.workspace = workspace; 31 | this.listener = listener; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/ParameterizedRemoteTrigger/BuildContext.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.ParameterizedRemoteTrigger; 2 | 3 | import static org.apache.commons.lang.StringUtils.trimToNull; 4 | 5 | import java.io.PrintStream; 6 | 7 | import edu.umd.cs.findbugs.annotations.NonNull; 8 | import edu.umd.cs.findbugs.annotations.Nullable; 9 | 10 | import org.jenkinsci.plugins.ParameterizedRemoteTrigger.pipeline.Handle; 11 | 12 | import hudson.FilePath; 13 | import hudson.model.Run; 14 | import hudson.model.TaskListener; 15 | 16 | /** 17 | * This object wraps a {@link Run}, {@link FilePath}, {@link TaskListener} and {@link PrintStream} - 18 | * the typical objects passed from one method to the other in a Jenkins Builder/BuildStep implementation.
19 | *
20 | * The reason for wrapping is simplicity on the one hand. On the other in an asynchronous pipeline usage 21 | * via the {@link Handle} we might not have a {@link Run}, {@link FilePath}, {@link TaskListener}, but we still 22 | * want to provide a {@link PrintStream} for logging. Therefore the first three objects can be null, the {@link PrintStream} 23 | * must not be null. 24 | */ 25 | public class BuildContext extends BasicBuildContext 26 | { 27 | @NonNull 28 | public final PrintStream logger; 29 | 30 | @NonNull 31 | public RemoteJenkinsServer effectiveRemoteServer; 32 | 33 | /** 34 | * The current Item (job, pipeline,...) where the plugin is used from. 35 | */ 36 | @NonNull 37 | public final String currentItem; 38 | 39 | 40 | public BuildContext(@Nullable Run run, @Nullable FilePath workspace, @Nullable TaskListener listener, @NonNull PrintStream logger, @NonNull RemoteJenkinsServer effectiveRemoteServer, @Nullable String currentItem) { 41 | super(run, workspace, listener); 42 | this.logger = logger; 43 | this.effectiveRemoteServer = effectiveRemoteServer; 44 | this.currentItem = getCurrentItem(run, currentItem); 45 | } 46 | 47 | public BuildContext(@Nullable Run run, @Nullable FilePath workspace, @Nullable TaskListener listener, @NonNull PrintStream logger, @NonNull RemoteJenkinsServer effectiveRemoteServer) { 48 | this(run, workspace, listener, logger, effectiveRemoteServer, null); 49 | } 50 | 51 | public BuildContext(@NonNull PrintStream logger, @NonNull RemoteJenkinsServer effectiveRemoteServer, @Nullable String currentItem) 52 | { 53 | this(null, null, null, logger, effectiveRemoteServer, currentItem); 54 | } 55 | 56 | @NonNull 57 | private String getCurrentItem(Run run, String currentItem) 58 | { 59 | String runItem = null; 60 | String curItem = trimToNull(currentItem); 61 | if(run != null && run.getParent() != null) { 62 | runItem = trimToNull(run.getParent().getFullName()); 63 | } 64 | if(runItem != null && curItem != null) { 65 | if(runItem.equals(curItem)) { 66 | return runItem; 67 | } else { 68 | throw new IllegalArgumentException(String.format("Current Item ('%s') and Parent Item from Run ('%s') differ!", curItem, runItem)); 69 | } 70 | } else if(runItem != null) { 71 | return runItem; 72 | } else if(curItem != null) { 73 | return curItem; 74 | } else { 75 | throw new IllegalArgumentException("Both null, Run and Current Item!"); 76 | } 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/ParameterizedRemoteTrigger/ConnectionResponse.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.ParameterizedRemoteTrigger; 2 | 3 | import edu.umd.cs.findbugs.annotations.NonNull; 4 | 5 | import net.sf.json.JSONObject; 6 | 7 | import edu.umd.cs.findbugs.annotations.CheckForNull; 8 | import edu.umd.cs.findbugs.annotations.Nullable; 9 | import java.util.List; 10 | import java.util.Map; 11 | import java.util.TreeMap; 12 | import java.util.stream.Collectors; 13 | 14 | /** 15 | * Http response containing header, body (JSON format) and response code. 16 | * 17 | */ 18 | public class ConnectionResponse 19 | { 20 | @NonNull 21 | private final Map> header = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); 22 | 23 | @Nullable @CheckForNull 24 | private final JSONObject body; 25 | 26 | @Nullable @CheckForNull 27 | private final String rawBody; 28 | 29 | @NonNull 30 | private final int responseCode; 31 | 32 | 33 | public ConnectionResponse(@NonNull Map> header, @Nullable JSONObject body, @NonNull int responseCode) 34 | { 35 | loadHeader(header); 36 | this.body = body; 37 | this.rawBody = null; 38 | this.responseCode = responseCode; 39 | } 40 | 41 | public ConnectionResponse(@NonNull Map> header, @Nullable String rawBody, @NonNull int responseCode) 42 | { 43 | loadHeader(header); 44 | this.body = null; 45 | this.rawBody = rawBody; 46 | this.responseCode = responseCode; 47 | } 48 | 49 | public ConnectionResponse(@NonNull Map> header, @NonNull int responseCode) 50 | { 51 | loadHeader(header); 52 | this.body = null; 53 | this.rawBody = null; 54 | this.responseCode = responseCode; 55 | } 56 | 57 | private void loadHeader(Map> header) { 58 | // null key is not compatible with the string Comparator, so we leave it out. 59 | Map> filtered = header.entrySet().stream().filter(entry -> entry.getKey() != null).collect( 60 | Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); 61 | this.header.putAll(filtered); 62 | } 63 | 64 | public Map> getHeader() 65 | { 66 | return header; 67 | } 68 | 69 | public JSONObject getBody() { 70 | return body; 71 | } 72 | 73 | public String getRawBody() { 74 | return rawBody; 75 | } 76 | 77 | public int getResponseCode() { 78 | return responseCode; 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/ParameterizedRemoteTrigger/JenkinsCrumb.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.ParameterizedRemoteTrigger; 2 | 3 | /** 4 | * If the remote Jenkins server uses the "Prevent Cross Site Request Forgery exploits" security option, 5 | * a CSRF protection token must be sent in the header of the request to trigger the remote job. 6 | * This token is called crumb. 7 | * 8 | */ 9 | public class JenkinsCrumb 10 | { 11 | String headerId; 12 | String crumbValue; 13 | boolean isEnabledOnRemote; 14 | 15 | /** 16 | * New JenkinsCrumb object indicating that CSRF is disabled in the remote Jenkins (no crumb needed). 17 | */ 18 | public JenkinsCrumb() 19 | { 20 | this.headerId = null; 21 | this.crumbValue = null; 22 | this.isEnabledOnRemote = false; 23 | } 24 | 25 | /** 26 | * New JenkinsCrumb object with the header ID and crumb value to use in subsequent requests. 27 | * 28 | * @param headerId 29 | * the header ID to be used in the subsequent requests. 30 | * @param crumbValue 31 | * the crumb value to be used in the header of subsequent requests. 32 | */ 33 | public JenkinsCrumb(String headerId, String crumbValue) 34 | { 35 | this.headerId = headerId; 36 | this.crumbValue = crumbValue; 37 | this.isEnabledOnRemote = true; 38 | } 39 | 40 | /** 41 | * @return the header ID to be used in the subsequent requests. Null if CSRF is disabled in the remote Jenkins. 42 | */ 43 | public String getHeaderId() 44 | { 45 | return headerId; 46 | } 47 | 48 | /** 49 | * @return the crumb value to be used in the header of subsequent requests. Null if CSRF is disabled in the remote Jenkins. 50 | */ 51 | public String getCrumbValue() 52 | { 53 | return crumbValue; 54 | } 55 | 56 | /** 57 | * @return true if CSRF is enabled on the remote Jenkins, false otherwise. 58 | */ 59 | public boolean isEnabledOnRemote() 60 | { 61 | return isEnabledOnRemote; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/ParameterizedRemoteTrigger/RemoteJenkinsServer.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.ParameterizedRemoteTrigger; 2 | 3 | import static org.apache.commons.lang.StringUtils.trimToEmpty; 4 | 5 | import java.io.Serializable; 6 | import javax.net.ssl.*; 7 | import java.net.URL; 8 | import java.security.KeyManagementException; 9 | import java.security.NoSuchAlgorithmException; 10 | import java.security.SecureRandom; 11 | import java.util.List; 12 | 13 | import edu.umd.cs.findbugs.annotations.CheckForNull; 14 | 15 | import org.jenkinsci.plugins.ParameterizedRemoteTrigger.auth2.Auth2; 16 | import org.jenkinsci.plugins.ParameterizedRemoteTrigger.auth2.Auth2.Auth2Descriptor; 17 | import org.jenkinsci.plugins.ParameterizedRemoteTrigger.auth2.NoneAuth; 18 | import org.jenkinsci.plugins.ParameterizedRemoteTrigger.utils.NaiveTrustManager; 19 | import org.kohsuke.accmod.Restricted; 20 | import org.kohsuke.accmod.restrictions.NoExternalUse; 21 | import org.kohsuke.stapler.DataBoundConstructor; 22 | import org.kohsuke.stapler.DataBoundSetter; 23 | import org.kohsuke.stapler.QueryParameter; 24 | 25 | import hudson.Extension; 26 | import hudson.model.AbstractDescribableImpl; 27 | import hudson.model.Descriptor; 28 | import hudson.util.FormValidation; 29 | 30 | /** 31 | * Holds everything regarding the remote server we wish to connect to, including validations and what not. 32 | * 33 | * @author Maurice W. 34 | * 35 | */ 36 | public class RemoteJenkinsServer extends AbstractDescribableImpl implements Cloneable, Serializable { 37 | 38 | private static final long serialVersionUID = -9211781849078964416L; 39 | 40 | /** 41 | * Default for this class is No Authentication 42 | */ 43 | private static final Auth2 DEFAULT_AUTH = NoneAuth.INSTANCE; 44 | 45 | /** 46 | * We need to keep this for compatibility - old config deserialization! 47 | * @deprecated since 2.3.0-SNAPSHOT - use {@link Auth2} instead. 48 | */ 49 | @CheckForNull 50 | private transient List auth; 51 | 52 | @CheckForNull 53 | private String displayName; 54 | private boolean hasBuildTokenRootSupport; 55 | private boolean trustAllCertificates; 56 | private boolean overrideTrustAllCertificates; 57 | 58 | @CheckForNull 59 | private Auth2 auth2; 60 | @CheckForNull 61 | private String address; 62 | private boolean useProxy; 63 | 64 | @DataBoundConstructor 65 | public RemoteJenkinsServer() { 66 | } 67 | 68 | /* 69 | * see https://wiki.jenkins.io/display/JENKINS/Hint+on+retaining+backward+compatibility 70 | */ 71 | @SuppressWarnings("deprecation") 72 | protected Object readResolve() { 73 | //migrate Auth To Auth2 74 | if(auth2 == null) { 75 | if(auth == null || auth.size() <= 0) { 76 | auth2 = DEFAULT_AUTH; 77 | } else { 78 | auth2 = Auth.authToAuth2(auth); 79 | } 80 | } 81 | auth = null; 82 | return this; 83 | } 84 | 85 | @DataBoundSetter 86 | public void setTrustAllCertificates(boolean trustAllCertificates) { 87 | this.trustAllCertificates = trustAllCertificates; 88 | } 89 | 90 | @DataBoundSetter 91 | public void setOverrideTrustAllCertificates(boolean overrideTrustAllCertificates) { 92 | this.overrideTrustAllCertificates = overrideTrustAllCertificates; 93 | } 94 | 95 | @DataBoundSetter 96 | public void setDisplayName(String displayName) 97 | { 98 | this.displayName = trimToEmpty(displayName); 99 | } 100 | 101 | @DataBoundSetter 102 | public void setHasBuildTokenRootSupport(boolean hasBuildTokenRootSupport) 103 | { 104 | this.hasBuildTokenRootSupport = hasBuildTokenRootSupport; 105 | } 106 | 107 | @DataBoundSetter 108 | public void setUseProxy(boolean useProxy) { 109 | this.useProxy = useProxy; 110 | } 111 | 112 | @DataBoundSetter 113 | public void setAuth2(Auth2 auth2) 114 | { 115 | this.auth2 = (auth2 != null) ? auth2 : DEFAULT_AUTH; 116 | } 117 | 118 | @DataBoundSetter 119 | public void setAddress(String address) 120 | { 121 | this.address = address; 122 | } 123 | 124 | // Getters 125 | 126 | @CheckForNull 127 | public String getDisplayName() { 128 | String displayName = null; 129 | 130 | if (this.displayName == null || this.displayName.trim().equals("")) { 131 | if (address != null) displayName = address; 132 | else displayName = null; 133 | } else { 134 | displayName = this.displayName; 135 | } 136 | return displayName; 137 | } 138 | 139 | public boolean getHasBuildTokenRootSupport() { 140 | return hasBuildTokenRootSupport; 141 | } 142 | 143 | public boolean isUseProxy() { 144 | return useProxy; 145 | } 146 | 147 | @CheckForNull 148 | public Auth2 getAuth2() { 149 | return (auth2 != null) ? auth2 : NoneAuth.INSTANCE; 150 | } 151 | 152 | @CheckForNull 153 | public String getAddress() { 154 | return address; 155 | } 156 | 157 | @Override 158 | public DescriptorImpl getDescriptor() { 159 | return (DescriptorImpl) super.getDescriptor(); 160 | } 161 | 162 | public boolean getTrustAllCertificates() { return trustAllCertificates; } 163 | 164 | public boolean getOverrideTrustAllCertificates() { return overrideTrustAllCertificates; } 165 | 166 | 167 | @Extension 168 | public static class DescriptorImpl extends Descriptor { 169 | 170 | public String getDisplayName() { 171 | return ""; 172 | } 173 | 174 | /** 175 | * Sets the TrustManager to be a "NaiveTrustManager", allowing us to ignore untrusted certificates 176 | * Will set the connection to null, if a key management error occurred. 177 | * 178 | * ATTENTION: THIS IS VERY DANGEROUS AND SHOULD ONLY BE USED IF YOU KNOW WHAT YOU DO! 179 | * @param conn The HttpsURLConnection you want to modify. 180 | * @param trustAllCertificates A boolean, gotten from the Remote Hosts description 181 | */ 182 | public void makeConnectionTrustAllCertificates(HttpsURLConnection conn, boolean trustAllCertificates) 183 | throws NoSuchAlgorithmException, KeyManagementException { 184 | if (trustAllCertificates) { 185 | SSLContext ctx = SSLContext.getInstance("TLS"); 186 | ctx.init(new KeyManager[0], new TrustManager[]{new NaiveTrustManager()}, new SecureRandom()); 187 | conn.setSSLSocketFactory(ctx.getSocketFactory()); 188 | 189 | // Trust every hostname 190 | HostnameVerifier allHostsValid = (hostname, session) -> true; 191 | conn.setHostnameVerifier(allHostsValid); 192 | } 193 | } 194 | 195 | /** 196 | * Validates the given address to see that it's well-formed, and is reachable. 197 | * 198 | * @param address 199 | * Remote address to be validated 200 | * @return FormValidation object 201 | */ 202 | @Restricted(NoExternalUse.class) 203 | public FormValidation doCheckAddress(@QueryParameter String address, @QueryParameter boolean trustAllCertificates) { 204 | 205 | URL host = null; 206 | 207 | // no empty addresses allowed 208 | if (address == null || address.trim().equals("")) { 209 | return FormValidation.warning("The remote address can not be empty, or it must be overridden on the job configuration."); 210 | } 211 | 212 | // check if we have a valid, well-formed URL 213 | try { 214 | host = new URL(address); 215 | host.toURI(); 216 | } catch (Exception e) { 217 | return FormValidation.error("Malformed address (" + address + "). Remember to indicate the protocol, i.e. http, https, etc."); 218 | } 219 | 220 | // check that the host is reachable 221 | try { 222 | HttpsURLConnection conn = (HttpsURLConnection) host.openConnection(); 223 | try { 224 | makeConnectionTrustAllCertificates(conn, trustAllCertificates); 225 | } catch (NoSuchAlgorithmException | KeyManagementException e) { 226 | return FormValidation.error(e, "A key management error occurred."); 227 | } 228 | conn.setConnectTimeout(5000); 229 | conn.connect(); 230 | 231 | if (trustAllCertificates) { 232 | return FormValidation.warning( 233 | "Connection established! Accepting all certificates is potentially unsafe." 234 | ); 235 | } 236 | } catch (Exception e) { 237 | return FormValidation.warning("Address looks good, but a connection could not be established."); 238 | } 239 | 240 | return FormValidation.ok(); 241 | } 242 | 243 | public static List getAuth2Descriptors() { 244 | return Auth2.all(); 245 | } 246 | 247 | public static Auth2Descriptor getDefaultAuth2Descriptor() { 248 | return NoneAuth.DESCRIPTOR; 249 | } 250 | } 251 | 252 | @Override 253 | public RemoteJenkinsServer clone() throws CloneNotSupportedException { 254 | RemoteJenkinsServer clone = (RemoteJenkinsServer)super.clone(); 255 | clone.auth2 = (auth2 == null) ? null : auth2.clone(); 256 | return clone; 257 | } 258 | 259 | @Override 260 | public int hashCode() { 261 | final int prime = 31; 262 | int result = 1; 263 | result = prime * result + ((address == null) ? 0 : address.hashCode()); 264 | result = prime * result + ((auth2 == null) ? 0 : auth2.hashCode()); 265 | result = prime * result + ((displayName == null) ? 0 : displayName.hashCode()); 266 | result = prime * result + (hasBuildTokenRootSupport ? 1231 : 1237); 267 | return result; 268 | } 269 | 270 | @Override 271 | public boolean equals(Object obj) { 272 | if (this == obj) 273 | return true; 274 | if (obj == null) 275 | return false; 276 | if (! (obj instanceof RemoteJenkinsServer)) 277 | return false; 278 | RemoteJenkinsServer other = (RemoteJenkinsServer) obj; 279 | if (address == null) { 280 | if (other.address != null) 281 | return false; 282 | } else if (!address.equals(other.address)) 283 | return false; 284 | if (auth2 == null) { 285 | if (other.auth2 != null) 286 | return false; 287 | } else if (!auth2.equals(other.auth2)) 288 | return false; 289 | if (displayName == null) { 290 | if (other.displayName != null) 291 | return false; 292 | } else if (!displayName.equals(other.displayName)) 293 | return false; 294 | if (hasBuildTokenRootSupport != other.hasBuildTokenRootSupport) 295 | return false; 296 | return true; 297 | } 298 | } 299 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/ParameterizedRemoteTrigger/auth2/Auth2.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.ParameterizedRemoteTrigger.auth2; 2 | 3 | import java.io.IOException; 4 | import java.io.Serializable; 5 | import java.net.URLConnection; 6 | 7 | import org.jenkinsci.plugins.ParameterizedRemoteTrigger.BuildContext; 8 | 9 | import hudson.DescriptorExtensionList; 10 | import hudson.model.AbstractDescribableImpl; 11 | import hudson.model.Descriptor; 12 | import hudson.model.Item; 13 | import jenkins.model.Jenkins; 14 | 15 | public abstract class Auth2 extends AbstractDescribableImpl implements Serializable, Cloneable { 16 | 17 | private static final long serialVersionUID = -3217381962636283564L; 18 | 19 | private static final DescriptorExtensionList ALL = DescriptorExtensionList 20 | .createDescriptorList(Jenkins.getInstance(), Auth2.class); 21 | 22 | public static DescriptorExtensionList all() 23 | { 24 | return ALL; 25 | } 26 | 27 | public static abstract class Auth2Descriptor extends Descriptor 28 | { 29 | } 30 | 31 | /** 32 | * Depending on the purpose the Auth2 implementation has to override the 33 | * Authorization header of the connection appropriately. It might also ignore this 34 | * step or remove an existing Authorization header. 35 | * 36 | * @param connection 37 | * connection between the application and the remote server. 38 | * @param context 39 | * the context of this Builder/BuildStep. 40 | * @throws IOException 41 | * if there is an error generating the authorization header. 42 | */ 43 | public abstract void setAuthorizationHeader(URLConnection connection, BuildContext context) throws IOException; 44 | 45 | public abstract String toString(); 46 | 47 | /** 48 | * Returns a string representing the authorization. 49 | * 50 | * @param item 51 | * the Item (Job, Pipeline,...) we are currently running in. 52 | * The item is required to also get Credentials which are defined in the items scope and not Jenkins globally. 53 | * Value can be null, but Credentials e.g. configured on a Folder will not be found in this case, 54 | * only globally configured Credentials. 55 | * @return a string representing the authorization. 56 | */ 57 | public abstract String toString(Item item); 58 | 59 | 60 | @Override 61 | public Auth2 clone() throws CloneNotSupportedException { 62 | return (Auth2)super.clone(); 63 | }; 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/ParameterizedRemoteTrigger/auth2/BearerTokenAuth.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.ParameterizedRemoteTrigger.auth2; 2 | 3 | import java.io.IOException; 4 | import java.net.URLConnection; 5 | 6 | import org.jenkinsci.Symbol; 7 | import org.jenkinsci.plugins.ParameterizedRemoteTrigger.BuildContext; 8 | import org.kohsuke.stapler.DataBoundConstructor; 9 | import org.kohsuke.stapler.DataBoundSetter; 10 | 11 | import hudson.Extension; 12 | import hudson.model.Item; 13 | import hudson.util.Secret; 14 | 15 | 16 | public class BearerTokenAuth extends Auth2 { 17 | 18 | private static final long serialVersionUID = 3614172320192170597L; 19 | 20 | @Extension 21 | public static final Auth2Descriptor DESCRIPTOR = new BearerTokenAuthDescriptor(); 22 | 23 | private Secret token; 24 | 25 | @DataBoundConstructor 26 | public BearerTokenAuth() { 27 | this.token = null; 28 | } 29 | 30 | @DataBoundSetter 31 | public void setToken(Secret token) { 32 | this.token = token; 33 | } 34 | 35 | public Secret getToken() { 36 | return this.token; 37 | } 38 | 39 | @Override 40 | public void setAuthorizationHeader(URLConnection connection, BuildContext context) throws IOException { 41 | connection.setRequestProperty("Authorization", "Bearer: " + getToken().getPlainText()); 42 | } 43 | 44 | @Override 45 | public String toString() { 46 | return "'" + getDescriptor().getDisplayName() + "'"; 47 | } 48 | 49 | @Override 50 | public String toString(Item item) { 51 | return toString(); 52 | } 53 | 54 | @Override 55 | public Auth2Descriptor getDescriptor() { 56 | return DESCRIPTOR; 57 | } 58 | 59 | @Symbol("BearerTokenAuth") 60 | public static class BearerTokenAuthDescriptor extends Auth2Descriptor { 61 | @Override 62 | public String getDisplayName() { 63 | return "Bearer Token Authentication"; 64 | } 65 | } 66 | 67 | @Override 68 | public int hashCode() { 69 | final int prime = 31; 70 | int result = 1; 71 | result = prime * result + ((token == null) ? 0 : token.hashCode()); 72 | return result; 73 | } 74 | 75 | @Override 76 | public boolean equals(Object obj) { 77 | if (this == obj) 78 | return true; 79 | if (obj == null) 80 | return false; 81 | if (!this.getClass().isInstance(obj)) 82 | return false; 83 | BearerTokenAuth other = (BearerTokenAuth) obj; 84 | if (token == null) { 85 | if (other.token == null) { 86 | return true; 87 | } else { 88 | return false; 89 | } 90 | } 91 | return token.equals(other.token); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/ParameterizedRemoteTrigger/auth2/CredentialsAuth.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.ParameterizedRemoteTrigger.auth2; 2 | 3 | import static org.jenkinsci.plugins.ParameterizedRemoteTrigger.utils.Base64Utils.AUTHTYPE_BASIC; 4 | 5 | import java.io.IOException; 6 | import java.net.URLConnection; 7 | import java.util.ArrayList; 8 | import java.util.Collections; 9 | import java.util.List; 10 | 11 | import org.jenkinsci.Symbol; 12 | import org.jenkinsci.plugins.ParameterizedRemoteTrigger.BuildContext; 13 | import org.jenkinsci.plugins.ParameterizedRemoteTrigger.exceptions.CredentialsNotFoundException; 14 | import org.jenkinsci.plugins.ParameterizedRemoteTrigger.utils.Base64Utils; 15 | import org.kohsuke.stapler.DataBoundConstructor; 16 | import org.kohsuke.stapler.DataBoundSetter; 17 | import org.kohsuke.stapler.Stapler; 18 | 19 | import com.cloudbees.plugins.credentials.CredentialsProvider; 20 | import com.cloudbees.plugins.credentials.common.StandardUsernameCredentials; 21 | import com.cloudbees.plugins.credentials.common.StandardUsernameListBoxModel; 22 | import com.cloudbees.plugins.credentials.common.UsernamePasswordCredentials; 23 | import com.cloudbees.plugins.credentials.domains.DomainRequirement; 24 | 25 | import hudson.Extension; 26 | import hudson.model.Item; 27 | import hudson.security.ACL; 28 | import hudson.util.ListBoxModel; 29 | import jenkins.model.Jenkins; 30 | 31 | public class CredentialsAuth extends Auth2 { 32 | 33 | private static final long serialVersionUID = -2650007108928532552L; 34 | 35 | @Extension 36 | public static final Auth2Descriptor DESCRIPTOR = new CredentialsAuthDescriptor(); 37 | 38 | private String credentials; 39 | 40 | @DataBoundConstructor 41 | public CredentialsAuth() { 42 | this.credentials = null; 43 | } 44 | 45 | @DataBoundSetter 46 | public void setCredentials(String credentials) { 47 | this.credentials = credentials; 48 | } 49 | 50 | public String getCredentials() { 51 | return credentials; 52 | } 53 | 54 | /** 55 | * Tries to find the Jenkins Credential and returns the user name. 56 | * @param item the Item (Job, Pipeline,...) we are currently running in. 57 | * The item is required to also get Credentials which are defined in the items scope and not Jenkins globally. 58 | * Value can be null, but Credentials e.g. configured on a Folder will not be found in this case, only globally configured Credentials. 59 | * @return The user name configured in this Credential 60 | * @throws CredentialsNotFoundException if credential could not be found. 61 | */ 62 | public String getUserName(Item item) throws CredentialsNotFoundException { 63 | UsernamePasswordCredentials creds = _getCredentials(item); 64 | return creds.getUsername(); 65 | } 66 | 67 | /** 68 | * Tries to find the Jenkins Credential and returns the password. 69 | * @param item the Item (Job, Pipeline,...) we are currently running in. 70 | * The item is required to also get Credentials which are defined in the items scope and not Jenkins globally. 71 | * Value can be null, but Credentials e.g. configured on a Folder will not be found in this case, only globally configured Credentials. 72 | * @return The password configured in this Credential 73 | * @throws CredentialsNotFoundException if credential could not be found. 74 | */ 75 | public String getPassword(Item item) throws CredentialsNotFoundException { 76 | UsernamePasswordCredentials creds = _getCredentials(item); 77 | return creds.getPassword().getPlainText(); 78 | } 79 | 80 | @Override 81 | public String toString() { 82 | return toString(null); 83 | } 84 | 85 | @Override 86 | public String toString(Item item) { 87 | try { 88 | String userName = getUserName(item); 89 | return String.format("'%s' as user '%s' (Credentials ID '%s')", getDescriptor().getDisplayName(), userName, credentials); 90 | } 91 | catch (CredentialsNotFoundException e) { 92 | return String.format("'%s'. WARNING! No credentials found with ID '%s'!", getDescriptor().getDisplayName(), credentials); 93 | } 94 | } 95 | 96 | /** 97 | * Looks up the credentialsID attached to this object in the Global Credentials plugin datastore 98 | * @param item the Item (Job, Pipeline,...) we are currently running in. 99 | * The item is required to also get Credentials which are defined in the items scope and not Jenkins globally. 100 | * Value can be null, but Credentials e.g. configured on a Folder will not be found in this case, only globally configured Credentials. 101 | * @return the matched credentials 102 | * @throws CredentialsNotFoundException if not found 103 | */ 104 | private UsernamePasswordCredentials _getCredentials(Item item) throws CredentialsNotFoundException { 105 | List listOfCredentials = CredentialsProvider.lookupCredentials( 106 | StandardUsernameCredentials.class, item, ACL.SYSTEM, Collections. emptyList()); 107 | 108 | return (UsernamePasswordCredentials) _findCredential(credentials, listOfCredentials); 109 | } 110 | 111 | private StandardUsernameCredentials _findCredential(String credentialId, List listOfCredentials) throws CredentialsNotFoundException{ 112 | for (StandardUsernameCredentials cred : listOfCredentials) { 113 | if (credentialId.equals(cred.getId())) { 114 | return cred; 115 | } 116 | } 117 | throw new CredentialsNotFoundException(credentialId); 118 | } 119 | 120 | @Override 121 | public void setAuthorizationHeader(URLConnection connection, BuildContext context) throws IOException { 122 | Jenkins jenkins = Jenkins.getInstance(); 123 | if (jenkins != null) { 124 | Item item = jenkins.getItem(context.currentItem, jenkins.getItem("/")); 125 | String authHeaderValue = Base64Utils.generateAuthorizationHeaderValue(AUTHTYPE_BASIC, getUserName(item), getPassword(item), context, false); 126 | connection.setRequestProperty("Authorization", authHeaderValue); 127 | } 128 | } 129 | 130 | @Override 131 | public Auth2Descriptor getDescriptor() { 132 | return DESCRIPTOR; 133 | } 134 | 135 | @Symbol("CredentialsAuth") 136 | public static class CredentialsAuthDescriptor extends Auth2Descriptor { 137 | @Override 138 | public String getDisplayName() { 139 | return "Credentials Authentication"; 140 | } 141 | 142 | public static ListBoxModel doFillCredentialsItems() { 143 | StandardUsernameListBoxModel model = new StandardUsernameListBoxModel(); 144 | 145 | Item item = Stapler.getCurrentRequest2().findAncestorObject(Item.class); 146 | 147 | List listOfAllCredentails = CredentialsProvider.lookupCredentials( 148 | StandardUsernameCredentials.class, item, ACL.SYSTEM, Collections. emptyList()); 149 | 150 | List listOfSandardUsernameCredentials = new ArrayList(); 151 | 152 | // since we only care about 'UsernamePasswordCredentials' objects, lets seek those out and ignore the rest. 153 | for (StandardUsernameCredentials c : listOfAllCredentails) { 154 | if (c instanceof UsernamePasswordCredentials) { 155 | listOfSandardUsernameCredentials.add(c); 156 | } 157 | } 158 | model.withAll(listOfSandardUsernameCredentials); 159 | 160 | return model; 161 | } 162 | } 163 | 164 | @Override 165 | public int hashCode() { 166 | final int prime = 31; 167 | int result = 1; 168 | result = prime * result + ((credentials == null) ? 0 : credentials.hashCode()); 169 | return result; 170 | } 171 | 172 | @Override 173 | public boolean equals(Object obj) { 174 | if (this == obj) 175 | return true; 176 | if (obj == null) 177 | return false; 178 | if (!this.getClass().isInstance(obj)) 179 | return false; 180 | CredentialsAuth other = (CredentialsAuth) obj; 181 | if (credentials == null) { 182 | if (other.credentials != null) 183 | return false; 184 | } else if (!credentials.equals(other.credentials)) 185 | return false; 186 | return true; 187 | } 188 | 189 | } 190 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/ParameterizedRemoteTrigger/auth2/NoneAuth.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.ParameterizedRemoteTrigger.auth2; 2 | 3 | import java.io.IOException; 4 | import java.net.URLConnection; 5 | 6 | import org.jenkinsci.Symbol; 7 | import org.jenkinsci.plugins.ParameterizedRemoteTrigger.BuildContext; 8 | import org.kohsuke.stapler.DataBoundConstructor; 9 | 10 | import hudson.Extension; 11 | import hudson.model.Item; 12 | 13 | public class NoneAuth extends Auth2 { 14 | 15 | private static final long serialVersionUID = -3128995428538415113L; 16 | 17 | @Extension 18 | public static final Auth2Descriptor DESCRIPTOR = new NoneAuthDescriptor(); 19 | 20 | public static final NoneAuth INSTANCE = new NoneAuth(); 21 | 22 | 23 | @DataBoundConstructor 24 | public NoneAuth() { 25 | } 26 | 27 | @Override 28 | public void setAuthorizationHeader(URLConnection connection, BuildContext context) throws IOException { 29 | //TODO: Should remove potential existing header, but URLConnection does not provide means to do so. 30 | // Setting null worked in the past, but is not valid with newer versions (of Jetty). 31 | //connection.setRequestProperty("Authorization", null); 32 | } 33 | 34 | @Override 35 | public String toString() { 36 | return "'" + getDescriptor().getDisplayName() + "'"; 37 | } 38 | 39 | @Override 40 | public String toString(Item item) { 41 | return toString(); 42 | } 43 | 44 | @Override 45 | public Auth2Descriptor getDescriptor() { 46 | return DESCRIPTOR; 47 | } 48 | 49 | @Symbol("NoneAuth") 50 | public static class NoneAuthDescriptor extends Auth2Descriptor { 51 | @Override 52 | public String getDisplayName() { 53 | return "No Authentication"; 54 | } 55 | } 56 | 57 | @Override 58 | public int hashCode() { 59 | return "NoneAuth".hashCode(); 60 | } 61 | 62 | @Override 63 | public boolean equals(Object obj) { 64 | return this.getClass().isInstance(obj); 65 | } 66 | 67 | } -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/ParameterizedRemoteTrigger/auth2/NullAuth.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.ParameterizedRemoteTrigger.auth2; 2 | 3 | import java.io.IOException; 4 | import java.net.URLConnection; 5 | 6 | import org.jenkinsci.Symbol; 7 | import org.jenkinsci.plugins.ParameterizedRemoteTrigger.BuildContext; 8 | import org.kohsuke.stapler.DataBoundConstructor; 9 | 10 | import hudson.Extension; 11 | import hudson.model.Item; 12 | 13 | public class NullAuth extends Auth2 { 14 | 15 | private static final long serialVersionUID = -1209658644855942360L; 16 | 17 | @Extension 18 | public static final Auth2Descriptor DESCRIPTOR = new NullAuthDescriptor(); 19 | 20 | public static final NullAuth INSTANCE = new NullAuth(); 21 | 22 | @DataBoundConstructor 23 | public NullAuth() { 24 | } 25 | 26 | @Override 27 | public void setAuthorizationHeader(URLConnection connection, BuildContext context) throws IOException { 28 | //Ignore 29 | } 30 | 31 | @Override 32 | public String toString() { 33 | return "'" + getDescriptor().getDisplayName() + "'"; 34 | } 35 | 36 | @Override 37 | public String toString(Item item) { 38 | return toString(); 39 | } 40 | 41 | @Override 42 | public Auth2Descriptor getDescriptor() { 43 | return DESCRIPTOR; 44 | } 45 | 46 | @Symbol("NullAuth") 47 | public static class NullAuthDescriptor extends Auth2Descriptor { 48 | @Override 49 | public String getDisplayName() { 50 | return "Don't Set/Override"; 51 | } 52 | } 53 | 54 | @Override 55 | public int hashCode() { 56 | return "NullAuth".hashCode(); 57 | } 58 | 59 | @Override 60 | public boolean equals(Object obj) { 61 | return this.getClass().isInstance(obj); 62 | } 63 | 64 | } -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/ParameterizedRemoteTrigger/auth2/TokenAuth.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.ParameterizedRemoteTrigger.auth2; 2 | 3 | import static org.jenkinsci.plugins.ParameterizedRemoteTrigger.utils.Base64Utils.AUTHTYPE_BASIC; 4 | 5 | import java.io.IOException; 6 | import java.net.URLConnection; 7 | 8 | import org.jenkinsci.Symbol; 9 | import org.jenkinsci.plugins.ParameterizedRemoteTrigger.BuildContext; 10 | import org.jenkinsci.plugins.ParameterizedRemoteTrigger.utils.Base64Utils; 11 | import org.kohsuke.stapler.DataBoundConstructor; 12 | import org.kohsuke.stapler.DataBoundSetter; 13 | 14 | import hudson.Extension; 15 | import hudson.model.Item; 16 | import hudson.util.Secret; 17 | 18 | public class TokenAuth extends Auth2 { 19 | 20 | private static final long serialVersionUID = 7912089565969112023L; 21 | 22 | @Extension 23 | public static final Auth2Descriptor DESCRIPTOR = new TokenAuthDescriptor(); 24 | 25 | private String userName; 26 | private Secret apiToken; 27 | 28 | @DataBoundConstructor 29 | public TokenAuth() { 30 | this.userName = null; 31 | this.apiToken = null; 32 | } 33 | 34 | @DataBoundSetter 35 | public void setUserName(String userName) { 36 | this.userName = userName; 37 | } 38 | 39 | public String getUserName() { 40 | return this.userName; 41 | } 42 | 43 | @DataBoundSetter 44 | public void setApiToken(Secret apiToken) { 45 | this.apiToken = apiToken; 46 | } 47 | 48 | public Secret getApiToken() { 49 | return this.apiToken; 50 | } 51 | 52 | @Override 53 | public void setAuthorizationHeader(URLConnection connection, BuildContext context) throws IOException { 54 | String authHeaderValue = Base64Utils.generateAuthorizationHeaderValue(AUTHTYPE_BASIC, getUserName(), getApiToken().getPlainText(), context, true); 55 | connection.setRequestProperty("Authorization", authHeaderValue); 56 | } 57 | 58 | @Override 59 | public String toString() { 60 | return "'" + getDescriptor().getDisplayName() + "' as user '" + getUserName() + "'"; 61 | } 62 | 63 | @Override 64 | public String toString(Item item) { 65 | return toString(); 66 | } 67 | 68 | @Override 69 | public Auth2Descriptor getDescriptor() { 70 | return DESCRIPTOR; 71 | } 72 | 73 | @Symbol("TokenAuth") 74 | public static class TokenAuthDescriptor extends Auth2Descriptor { 75 | @Override 76 | public String getDisplayName() { 77 | return "Token Authentication"; 78 | } 79 | } 80 | 81 | @Override 82 | public int hashCode() { 83 | final int prime = 31; 84 | int result = 1; 85 | result = prime * result + ((apiToken == null) ? 0 : apiToken.hashCode()); 86 | result = prime * result + ((userName == null) ? 0 : userName.hashCode()); 87 | return result; 88 | } 89 | 90 | @Override 91 | public boolean equals(Object obj) { 92 | if (this == obj) 93 | return true; 94 | if (obj == null) 95 | return false; 96 | if (!this.getClass().isInstance(obj)) 97 | return false; 98 | TokenAuth other = (TokenAuth) obj; 99 | if (apiToken == null) { 100 | if (other.apiToken != null) 101 | return false; 102 | } else if (!apiToken.equals(other.apiToken)) { 103 | return false; 104 | } 105 | if (userName == null) { 106 | if (other.userName != null) 107 | return false; 108 | } else if (!userName.equals(other.userName)) { 109 | return false; 110 | } 111 | return true; 112 | } 113 | 114 | } 115 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/ParameterizedRemoteTrigger/exceptions/CredentialsNotFoundException.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.ParameterizedRemoteTrigger.exceptions; 2 | 3 | import java.io.IOException; 4 | 5 | public class CredentialsNotFoundException extends IOException 6 | { 7 | 8 | private static final long serialVersionUID = -2489306184948013529L; 9 | private String credentialsId; 10 | 11 | public CredentialsNotFoundException(String credentialsId) 12 | { 13 | this.credentialsId = credentialsId; 14 | } 15 | 16 | @Override 17 | public String getMessage() 18 | { 19 | return "No Jenkins Credentials found with ID '" + credentialsId + "'"; 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/ParameterizedRemoteTrigger/exceptions/ExceedRetryLimitException.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.ParameterizedRemoteTrigger.exceptions; 2 | 3 | import java.io.IOException; 4 | 5 | public class ExceedRetryLimitException extends IOException { 6 | 7 | /** 8 | * 9 | */ 10 | private static final long serialVersionUID = 7817258508279153509L; 11 | 12 | @Override 13 | public String getMessage() { 14 | return "Max number of connection retries have been exceeded."; 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/ParameterizedRemoteTrigger/exceptions/ForbiddenException.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.ParameterizedRemoteTrigger.exceptions; 2 | 3 | import java.io.IOException; 4 | import java.net.URL; 5 | 6 | public class ForbiddenException extends IOException 7 | { 8 | 9 | private static final long serialVersionUID = -4049611671761455585L; 10 | private URL url; 11 | 12 | public ForbiddenException(URL url) 13 | { 14 | this.url = url; 15 | } 16 | 17 | @Override 18 | public String getMessage() 19 | { 20 | return "Server returned 403 - Forbidden. User does not have enough permissions for this request: " + url; 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/ParameterizedRemoteTrigger/exceptions/UnauthorizedException.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.ParameterizedRemoteTrigger.exceptions; 2 | 3 | import java.io.IOException; 4 | import java.net.URL; 5 | 6 | public class UnauthorizedException extends IOException 7 | { 8 | private static final long serialVersionUID = -7505703592596401545L; 9 | 10 | private URL url; 11 | 12 | public UnauthorizedException(URL url) 13 | { 14 | this.url = url; 15 | } 16 | 17 | @Override 18 | public String getMessage() 19 | { 20 | return "Server returned 401 - Unauthorized. Most likely there is something wrong with the provided credentials for this request: " + url; 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/ParameterizedRemoteTrigger/exceptions/UrlNotFoundException.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.ParameterizedRemoteTrigger.exceptions; 2 | 3 | import java.io.IOException; 4 | import java.net.URL; 5 | 6 | public class UrlNotFoundException extends IOException { 7 | 8 | private static final long serialVersionUID = -8787613112499246042L; 9 | private URL url; 10 | 11 | public UrlNotFoundException(URL url) 12 | { 13 | this.url = url; 14 | } 15 | 16 | @Override 17 | public String getMessage() 18 | { 19 | return "Server returned 404 - URL not found for this request: " + url; 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/ParameterizedRemoteTrigger/parameters2/FileParameters.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.ParameterizedRemoteTrigger.parameters2; 2 | 3 | import static java.nio.charset.StandardCharsets.UTF_8; 4 | import static java.util.stream.Collectors.joining; 5 | 6 | import java.io.BufferedReader; 7 | 8 | import edu.umd.cs.findbugs.annotations.NonNull; 9 | import java.io.IOException; 10 | import java.io.InputStreamReader; 11 | import java.util.Map; 12 | import java.util.Objects; 13 | 14 | import org.jenkinsci.Symbol; 15 | import org.jenkinsci.plugins.ParameterizedRemoteTrigger.BuildContext; 16 | import org.kohsuke.stapler.DataBoundConstructor; 17 | import org.kohsuke.stapler.DataBoundSetter; 18 | 19 | import hudson.AbortException; 20 | import hudson.Extension; 21 | import hudson.FilePath; 22 | 23 | public class FileParameters extends JobParameters { 24 | 25 | private static final long serialVersionUID = 3614172320192170597L; 26 | 27 | @Extension(ordinal = 0) 28 | public static final FileParametersDescriptor DESCRIPTOR = new FileParametersDescriptor(); 29 | 30 | private String filePath; 31 | 32 | @DataBoundConstructor 33 | public FileParameters() { 34 | this.filePath = null; 35 | } 36 | 37 | public FileParameters(String filePath) { 38 | this.filePath = filePath; 39 | } 40 | 41 | @DataBoundSetter 42 | public void setFilePath(final String filePath) { 43 | this.filePath = filePath; 44 | } 45 | 46 | public String getFilePath() { 47 | return filePath; 48 | } 49 | 50 | @Override 51 | public String toString() { 52 | return "(" + getClass().getSimpleName() + ") " + filePath; 53 | } 54 | 55 | @Override 56 | public FileParametersDescriptor getDescriptor() { 57 | return DESCRIPTOR; 58 | } 59 | 60 | @Override 61 | public Map getParametersMap(final BuildContext context) throws AbortException { 62 | final String parametersAsString = readParametersFile(context); 63 | return JobParameters.parseStringParameters(parametersAsString); 64 | } 65 | 66 | private String readParametersFile(final BuildContext context) throws AbortException { 67 | if (context.workspace == null) { 68 | throw new AbortException("Workspace is null but parameter file is used. Looks like this step was started with \"agent: none\""); 69 | } 70 | 71 | BufferedReader reader = null; 72 | try { 73 | final FilePath absoluteFilePath = context.workspace.child(getFilePath()); 74 | context.logger.printf("Loading parameters from file %s%n", absoluteFilePath.getRemote()); 75 | 76 | reader = new BufferedReader(new InputStreamReader(absoluteFilePath.read(), UTF_8)); 77 | return reader.lines().collect(joining("\n")); 78 | 79 | } catch (final InterruptedException | IOException e) { 80 | context.logger.printf("[WARNING] Failed loading parameters: %s%n", e.getMessage()); 81 | return ""; 82 | 83 | } finally { 84 | try { 85 | if (reader != null) { 86 | reader.close(); 87 | } 88 | } catch (final IOException ex) { 89 | ex.printStackTrace(); 90 | } 91 | } 92 | } 93 | 94 | @Symbol("FileParameters") 95 | public static class FileParametersDescriptor extends ParametersDescriptor { 96 | @NonNull 97 | @Override 98 | public String getDisplayName() { 99 | return "File parameters"; 100 | } 101 | } 102 | 103 | @Override 104 | public boolean equals(final Object o) { 105 | if (this == o) { 106 | return true; 107 | } 108 | if (o == null || getClass() != o.getClass()) { 109 | return false; 110 | } 111 | final FileParameters that = (FileParameters) o; 112 | return Objects.equals(filePath, that.filePath); 113 | } 114 | 115 | @Override 116 | public int hashCode() { 117 | return Objects.hash(filePath); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/ParameterizedRemoteTrigger/parameters2/JobParameters.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.ParameterizedRemoteTrigger.parameters2; 2 | 3 | import static com.google.common.base.Strings.isNullOrEmpty; 4 | import static java.util.stream.Collectors.toMap; 5 | 6 | import java.io.Serializable; 7 | import java.util.AbstractMap; 8 | import java.util.Arrays; 9 | import java.util.Map; 10 | import java.util.Map.Entry; 11 | import java.util.function.Predicate; 12 | 13 | import org.jenkinsci.plugins.ParameterizedRemoteTrigger.BuildContext; 14 | 15 | import hudson.AbortException; 16 | import hudson.DescriptorExtensionList; 17 | import hudson.model.AbstractDescribableImpl; 18 | import hudson.model.Descriptor; 19 | import jenkins.model.Jenkins; 20 | 21 | public abstract class JobParameters extends AbstractDescribableImpl implements Serializable, Cloneable { 22 | 23 | private static final DescriptorExtensionList ALL = 24 | DescriptorExtensionList.createDescriptorList(Jenkins.getInstance(), JobParameters.class); 25 | 26 | public static DescriptorExtensionList all() { 27 | return ALL; 28 | } 29 | 30 | public static JobParameters migrateOldParameters(final String parameters, final String parameterFile) { 31 | if (!isNullOrEmpty(parameterFile)) { 32 | return new FileParameters(parameterFile); 33 | } 34 | 35 | if (!isNullOrEmpty(parameters)) { 36 | return new StringParameters(parameters); 37 | } 38 | 39 | return new MapParameters(); 40 | } 41 | 42 | public static Map parseStringParameters(final String parametersAsString) { 43 | return Arrays.stream(parametersAsString.split("\\n")) 44 | .filter(not(JobParameters::isBlankLine)) 45 | .filter(not(JobParameters::isCommentedLine)) 46 | .filter(JobParameters::containsEqualSign) 47 | .map(JobParameters::splitParameterLine) 48 | .collect(toMap(Entry::getKey, Entry::getValue)); 49 | } 50 | 51 | private static Predicate not(Predicate t) { 52 | return t.negate(); 53 | } 54 | 55 | private static boolean isBlankLine(String line) { 56 | return line.trim().isEmpty(); 57 | } 58 | 59 | private static boolean isCommentedLine(String line) { 60 | return line.trim().startsWith("#"); 61 | } 62 | 63 | private static boolean containsEqualSign(String line) { 64 | return line.contains("="); 65 | } 66 | 67 | private static Entry splitParameterLine(String line) { 68 | final int firstIndexOfEqualSign = line.indexOf("="); 69 | return new AbstractMap.SimpleEntry<>( 70 | line.substring(0, firstIndexOfEqualSign), 71 | line.substring(firstIndexOfEqualSign + 1) 72 | ); 73 | } 74 | 75 | public static abstract class ParametersDescriptor extends Descriptor { } 76 | 77 | public abstract Map getParametersMap(final BuildContext context) throws AbortException; 78 | 79 | @Override 80 | public JobParameters clone() throws CloneNotSupportedException { 81 | return (JobParameters) super.clone(); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/ParameterizedRemoteTrigger/parameters2/MapParameter.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.ParameterizedRemoteTrigger.parameters2; 2 | 3 | import edu.umd.cs.findbugs.annotations.NonNull; 4 | 5 | import java.io.Serializable; 6 | import java.util.Objects; 7 | 8 | import org.jenkinsci.Symbol; 9 | import org.kohsuke.stapler.DataBoundConstructor; 10 | import org.kohsuke.stapler.DataBoundSetter; 11 | 12 | import hudson.Extension; 13 | import hudson.model.AbstractDescribableImpl; 14 | import hudson.model.Descriptor; 15 | 16 | public class MapParameter extends AbstractDescribableImpl implements Cloneable, Serializable { 17 | 18 | @Extension 19 | public static final MapParameterDescriptor DESCRIPTOR = new MapParameterDescriptor(); 20 | 21 | private String name; 22 | private String value; 23 | 24 | @DataBoundConstructor 25 | public MapParameter() { 26 | this("", ""); 27 | } 28 | 29 | public MapParameter(String name, String value) { 30 | this.name = name; 31 | this.value = value; 32 | } 33 | 34 | @DataBoundSetter 35 | public void setName(String name) { 36 | this.name = name; 37 | } 38 | 39 | @DataBoundSetter 40 | public void setValue(String value) { 41 | this.value = value; 42 | } 43 | 44 | public String getName() { 45 | return name; 46 | } 47 | 48 | public String getValue() { 49 | return value; 50 | } 51 | 52 | @Override 53 | public MapParameter clone() throws CloneNotSupportedException { 54 | return (MapParameter) super.clone(); 55 | } 56 | 57 | @Override 58 | public Descriptor getDescriptor() { 59 | return DESCRIPTOR; 60 | } 61 | 62 | @Symbol("MapParameter") 63 | public static class MapParameterDescriptor extends Descriptor { 64 | @NonNull 65 | @Override 66 | public String getDisplayName() { 67 | return "Map parameter"; 68 | } 69 | } 70 | 71 | @Override 72 | public boolean equals(final Object o) { 73 | if (this == o) { 74 | return true; 75 | } 76 | if (o == null || getClass() != o.getClass()) { 77 | return false; 78 | } 79 | final MapParameter that = (MapParameter) o; 80 | return Objects.equals(name, that.name) && Objects.equals(value, that.value); 81 | } 82 | 83 | @Override 84 | public int hashCode() { 85 | return Objects.hash(name, value); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/ParameterizedRemoteTrigger/parameters2/MapParameters.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.ParameterizedRemoteTrigger.parameters2; 2 | 3 | import static java.util.stream.Collectors.toMap; 4 | 5 | import edu.umd.cs.findbugs.annotations.NonNull; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | import java.util.Map; 10 | import java.util.Objects; 11 | 12 | import org.jenkinsci.Symbol; 13 | import org.jenkinsci.plugins.ParameterizedRemoteTrigger.BuildContext; 14 | import org.kohsuke.stapler.DataBoundConstructor; 15 | import org.kohsuke.stapler.DataBoundSetter; 16 | 17 | import hudson.Extension; 18 | 19 | public class MapParameters extends JobParameters { 20 | 21 | private static final long serialVersionUID = 3614172320192170597L; 22 | 23 | @Extension(ordinal = 2) 24 | public static final MapParametersDescriptor DESCRIPTOR = new MapParametersDescriptor(); 25 | 26 | private final List parameters = new ArrayList<>(); 27 | 28 | @DataBoundConstructor 29 | public MapParameters() { } 30 | 31 | public MapParameters(@NonNull Map parametersMap) { 32 | setParametersMap(parametersMap); 33 | } 34 | 35 | @DataBoundSetter 36 | public void setParameters(final List parameters) { 37 | this.parameters.clear(); 38 | if (parameters != null) { 39 | this.parameters.addAll(parameters); 40 | } 41 | } 42 | 43 | public void setParametersMap(final Map parametersMap) { 44 | this.parameters.clear(); 45 | if (parametersMap != null) { 46 | parametersMap 47 | .entrySet() 48 | .stream() 49 | .map(entry -> new MapParameter(entry.getKey(), entry.getValue())) 50 | .forEach(parameters::add); 51 | } 52 | } 53 | 54 | public List getParameters() { 55 | return parameters; 56 | } 57 | 58 | @Override 59 | public String toString() { 60 | return "(" + getClass().getSimpleName() + ") " + parameters; 61 | } 62 | 63 | @Override 64 | public MapParametersDescriptor getDescriptor() { 65 | return DESCRIPTOR; 66 | } 67 | 68 | @Override 69 | public Map getParametersMap(final BuildContext context) { 70 | return parameters 71 | .stream() 72 | .collect(toMap(MapParameter::getName, MapParameter::getValue)); 73 | } 74 | 75 | @Symbol("MapParameters") 76 | public static class MapParametersDescriptor extends ParametersDescriptor { 77 | @NonNull 78 | @Override 79 | public String getDisplayName() { 80 | return "Map parameters"; 81 | } 82 | } 83 | 84 | @Override 85 | public boolean equals(final Object o) { 86 | if (this == o) { 87 | return true; 88 | } 89 | if (o == null || getClass() != o.getClass()) { 90 | return false; 91 | } 92 | final MapParameters that = (MapParameters) o; 93 | return Objects.equals(parameters, that.parameters); 94 | } 95 | 96 | @Override 97 | public int hashCode() { 98 | return Objects.hash(parameters); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/ParameterizedRemoteTrigger/parameters2/StringParameters.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.ParameterizedRemoteTrigger.parameters2; 2 | 3 | import edu.umd.cs.findbugs.annotations.NonNull; 4 | 5 | import java.util.Map; 6 | import java.util.Objects; 7 | 8 | import org.jenkinsci.Symbol; 9 | import org.jenkinsci.plugins.ParameterizedRemoteTrigger.BuildContext; 10 | import org.kohsuke.stapler.DataBoundConstructor; 11 | import org.kohsuke.stapler.DataBoundSetter; 12 | 13 | import hudson.Extension; 14 | 15 | public class StringParameters extends JobParameters { 16 | 17 | private static final long serialVersionUID = 3614172320192170597L; 18 | 19 | @Extension(ordinal = 1) 20 | public static final StringParametersDescriptor DESCRIPTOR = new StringParametersDescriptor(); 21 | 22 | private String parameters; 23 | 24 | @DataBoundConstructor 25 | public StringParameters() { 26 | this.parameters = null; 27 | } 28 | 29 | public StringParameters(String parameters) { 30 | this.parameters = parameters; 31 | } 32 | 33 | @DataBoundSetter 34 | public void setParameters(final String parameters) { 35 | this.parameters = parameters; 36 | } 37 | 38 | public String getParameters() { 39 | return parameters; 40 | } 41 | 42 | @Override 43 | public String toString() { 44 | return "(" + getClass().getSimpleName() + ") " + parameters; 45 | } 46 | 47 | @Override 48 | public StringParametersDescriptor getDescriptor() { 49 | return DESCRIPTOR; 50 | } 51 | 52 | @Override 53 | public Map getParametersMap(final BuildContext context) { 54 | return JobParameters.parseStringParameters(parameters); 55 | } 56 | 57 | @Symbol("StringParameters") 58 | public static class StringParametersDescriptor extends ParametersDescriptor { 59 | @NonNull 60 | @Override 61 | public String getDisplayName() { 62 | return "String parameters"; 63 | } 64 | } 65 | 66 | @Override 67 | public boolean equals(final Object o) { 68 | if (this == o) { 69 | return true; 70 | } 71 | if (o == null || getClass() != o.getClass()) { 72 | return false; 73 | } 74 | final StringParameters that = (StringParameters) o; 75 | return Objects.equals(parameters, that.parameters); 76 | } 77 | 78 | @Override 79 | public int hashCode() { 80 | return Objects.hash(parameters); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/ParameterizedRemoteTrigger/pipeline/PrintStreamWrapper.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.ParameterizedRemoteTrigger.pipeline; 2 | 3 | import java.io.ByteArrayOutputStream; 4 | import java.io.IOException; 5 | import java.io.PrintStream; 6 | import java.io.UnsupportedEncodingException; 7 | 8 | import org.apache.commons.io.IOUtils; 9 | 10 | /** 11 | * Wrapper to provide a PrintStream for writing content to 12 | * and a corresponding getContent() method to get the content 13 | * which has been written to the PrintStream. 14 | * 15 | * The reason is from the async Pipeline Handle we don't have 16 | * an active TaskListener.getLogger() anymore this means everything 17 | * written to the PrintStream (logger) will not be printed to the Pipeline log. 18 | * Therefore we provide this PrintStream for logging and the content can be 19 | * obtained later via getContent(). 20 | */ 21 | public class PrintStreamWrapper 22 | { 23 | 24 | private final ByteArrayOutputStream byteStream; 25 | private final PrintStream printStream; 26 | 27 | public PrintStreamWrapper() throws UnsupportedEncodingException { 28 | byteStream = new ByteArrayOutputStream(); 29 | printStream = new PrintStream(byteStream, false, "UTF-8"); 30 | } 31 | 32 | public PrintStream getPrintStream() { 33 | return printStream; 34 | } 35 | 36 | /** 37 | * Returns all logs since creation and closes the streams. 38 | * 39 | * @return all logs. 40 | * @throws IOException 41 | * if UTF-8 charset is not supported. 42 | */ 43 | public String getContent() throws IOException { 44 | String string = byteStream.toString("UTF-8"); 45 | close(); 46 | return string; 47 | } 48 | 49 | /** 50 | * Closes the streams. 51 | */ 52 | public void close() { 53 | IOUtils.closeQuietly(printStream); 54 | IOUtils.closeQuietly(byteStream); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/ParameterizedRemoteTrigger/remoteJob/QueueItem.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.ParameterizedRemoteTrigger.remoteJob; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | 6 | import edu.umd.cs.findbugs.annotations.NonNull; 7 | 8 | import hudson.AbortException; 9 | 10 | /** 11 | * Represents an item on the queue. Contains information about the location 12 | * of the job in the queue. 13 | * 14 | */ 15 | public class QueueItem 16 | { 17 | final static private String key = "Location"; 18 | 19 | @NonNull 20 | private final String location; 21 | 22 | @NonNull 23 | private final String id; 24 | 25 | 26 | public QueueItem(@NonNull Map> header) throws AbortException 27 | { 28 | if (!header.containsKey(key)) 29 | throw new AbortException(String.format("Error triggering the remote job. The header of the response has an unexpected format: %n%s", header)); 30 | location = header.get(key).get(0); 31 | try { 32 | String loc = location.substring(0, location.lastIndexOf('/')); 33 | id = loc.substring(loc.lastIndexOf('/')+1); 34 | } catch (Exception ex) { 35 | throw new AbortException(String.format("Error triggering the remote job. The header of the response contains an unexpected location: %s", location)); 36 | } 37 | } 38 | 39 | @NonNull 40 | public String getLocation() { 41 | return location; 42 | } 43 | 44 | @NonNull 45 | public String getId() { 46 | return id; 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/ParameterizedRemoteTrigger/remoteJob/QueueItemData.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.ParameterizedRemoteTrigger.remoteJob; 2 | 3 | import java.net.MalformedURLException; 4 | import java.net.URL; 5 | 6 | import edu.umd.cs.findbugs.annotations.CheckForNull; 7 | import edu.umd.cs.findbugs.annotations.NonNull; 8 | import edu.umd.cs.findbugs.annotations.Nullable; 9 | 10 | import org.jenkinsci.plugins.ParameterizedRemoteTrigger.BuildContext; 11 | import net.sf.json.JSONException; 12 | 13 | import net.sf.json.JSONObject; 14 | 15 | /** 16 | * Contains information about the remote job while is waiting on the queue. 17 | * 18 | */ 19 | public class QueueItemData 20 | { 21 | @NonNull 22 | private QueueItemStatus status; 23 | 24 | @Nullable 25 | private String why; 26 | 27 | @NonNull 28 | private int buildNumber; 29 | 30 | @Nullable 31 | private URL buildURL; 32 | 33 | 34 | public QueueItemData() throws MalformedURLException 35 | { 36 | this.status = QueueItemStatus.WAITING; 37 | } 38 | 39 | public boolean isWaiting() 40 | { 41 | return status == QueueItemStatus.WAITING; 42 | } 43 | 44 | public boolean isBlocked() 45 | { 46 | return status == QueueItemStatus.BLOCKED; 47 | } 48 | 49 | public boolean isBuildable() 50 | { 51 | return status == QueueItemStatus.BUILDABLE; 52 | } 53 | 54 | public boolean isPending() 55 | { 56 | return status == QueueItemStatus.PENDING; 57 | } 58 | 59 | public boolean isLeft() 60 | { 61 | return status == QueueItemStatus.LEFT; 62 | } 63 | 64 | public boolean isExecuted() 65 | { 66 | return status == QueueItemStatus.EXECUTED; 67 | } 68 | 69 | public boolean isCancelled() 70 | { 71 | return status == QueueItemStatus.CANCELLED; 72 | } 73 | 74 | @NonNull 75 | public QueueItemStatus getStatus() { 76 | return status; 77 | } 78 | 79 | @CheckForNull 80 | public String getWhy() { 81 | return why; 82 | } 83 | 84 | @NonNull 85 | public int getBuildNumber() 86 | { 87 | return buildNumber; 88 | } 89 | 90 | @CheckForNull 91 | public URL getBuildURL() 92 | { 93 | return buildURL; 94 | } 95 | 96 | /** 97 | * Updates the queue item data with a queue response. 98 | * 99 | * @param context 100 | * the context of this Builder/BuildStep. 101 | * @param queueResponse 102 | * the queue response 103 | * @throws MalformedURLException 104 | * if there is an error creating the build URL. 105 | */ 106 | public void update(@NonNull BuildContext context, @NonNull JSONObject queueResponse) throws MalformedURLException 107 | { 108 | if (queueResponse.getBoolean("blocked")) status = QueueItemStatus.BLOCKED; 109 | if (queueResponse.getBoolean("buildable")) status = QueueItemStatus.BUILDABLE; 110 | if (getOptionalBoolean(queueResponse, "pending")) status = QueueItemStatus.PENDING; 111 | if (getOptionalBoolean(queueResponse, "cancelled")) status = QueueItemStatus.CANCELLED; 112 | if (isBlocked() || isBuildable() || isPending()) why = queueResponse.getString("why"); 113 | else if (!isCancelled()) status = QueueItemStatus.LEFT; 114 | 115 | if (isLeft()) { 116 | try { 117 | JSONObject remoteJobInfo = queueResponse.getJSONObject("executable"); 118 | if (!(remoteJobInfo.isNullObject())) { 119 | try { 120 | buildNumber = remoteJobInfo.getInt("number"); 121 | } catch (JSONException e) { 122 | context.logger.println(String.format("[WARNING] The attribute \"number\" was not found. Unexpected response: %s", queueResponse.toString())); 123 | } 124 | try { 125 | buildURL = new URL(remoteJobInfo.getString("url")); 126 | } catch (JSONException e) { 127 | context.logger.println(String.format("[WARNING] The attribute \"url\" was not found. Unexpected response: %s", queueResponse.toString())); 128 | } 129 | } 130 | } catch (JSONException e) { 131 | context.logger.println(String.format("[WARNING] The attribute \"executable\" was not found. Unexpected response: %s", queueResponse.toString())); 132 | } 133 | if (buildNumber != 0 && buildURL != null) status = QueueItemStatus.EXECUTED; 134 | } 135 | } 136 | 137 | private boolean getOptionalBoolean(@NonNull JSONObject queueResponse, @NonNull String attribute) 138 | { 139 | if (queueResponse.containsKey(attribute)) 140 | return queueResponse.getBoolean(attribute); 141 | else return false; 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/ParameterizedRemoteTrigger/remoteJob/QueueItemStatus.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.ParameterizedRemoteTrigger.remoteJob; 2 | 3 | /** 4 | * This class implements the status of an item while it is on the queue. 5 | *

6 | * See {@link hudson.model.Queue}. 7 | * 8 | *

{@code
 9 |  * (enter Queue) --> WAITING --+--> BLOCKED
10 |                           |          ^
11 |                           |          |
12 |                           |          v
13 |                           +-------> BUILDABLE ---> PENDING ---> LEFT --> EXECUTED
14 |                                        ^              |
15 |                                        |              |
16 |                                        +---(rarely)---+
17 |  *}
18 | * 19 | *

20 | * When the remote build is triggered, the remote job enters the queue (waiting list) 21 | * and the queue item status is WAITING. 22 | *

23 | * After that, if there is another build already in progress, the queue item status changes to BLOCKED. 24 | *

25 | * On the contrary, if there is not another build in progress, the queue item status changes to BUILDABLE. 26 | *

27 | * Once the queue item is buildable, it needs to wait for an available executor, and the status changes 28 | * to PENDING. 29 | *

30 | * If the node disappears before the execution starts, the status moves back to BUILDABLE, 31 | * but this is not the normal case. 32 | *

33 | * When there is an available executor and the execution starts, the queue item leaves the queue, 34 | * and the status changes to LEFT. 35 | *

36 | * When the remote job leaves the queue, the build number and the build URL are available. 37 | * The build URL can be used to request information about the remote job while it is being executed. 38 | *

39 | * Sometimes, we did face some issues, because the item left the queue but this properties where not available, 40 | * therefore the status EXECUTED was added. 41 | *

42 | * When this properties are available, the queue item status changes to EXECUTED. This is the final status. 43 | *

44 | * In addition, at any status, an item can be removed from the queue, in this case an AbortException is thrown. 45 | * 46 | */ 47 | public enum QueueItemStatus 48 | { 49 | /** 50 | * If a queue item enters the queue (waiting list). 51 | */ 52 | WAITING("WAITING"), 53 | 54 | /** 55 | * If another build is already in progress. 56 | */ 57 | BLOCKED("BLOCKED"), 58 | 59 | /** 60 | * If there is not another build in progress. 61 | */ 62 | BUILDABLE("BUILDABLE"), 63 | 64 | /** 65 | * If the queue item is waiting for an available executor. 66 | */ 67 | PENDING("PENDING"), 68 | 69 | /** 70 | * If there is an available executor and no build is already in progress. 71 | */ 72 | LEFT("LEFT"), 73 | 74 | /** 75 | * The queue item left the queue and the build information (build number and build URL) is available. 76 | */ 77 | EXECUTED("EXECUTED"), 78 | 79 | /** 80 | * If the queue item was cancelled and therefore it will not be executed. 81 | */ 82 | CANCELLED("CANCELLED"); 83 | 84 | 85 | private final String id; 86 | 87 | 88 | private QueueItemStatus(String id) { 89 | this.id = id; 90 | } 91 | 92 | @Override 93 | public String toString() { 94 | return id; 95 | } 96 | 97 | } -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/ParameterizedRemoteTrigger/remoteJob/RemoteBuildInfo.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.ParameterizedRemoteTrigger.remoteJob; 2 | 3 | import java.io.Serializable; 4 | import java.net.URL; 5 | 6 | import edu.umd.cs.findbugs.annotations.CheckForNull; 7 | import edu.umd.cs.findbugs.annotations.NonNull; 8 | import edu.umd.cs.findbugs.annotations.Nullable; 9 | 10 | import hudson.AbortException; 11 | import hudson.model.Result; 12 | 13 | /** 14 | * This class contains information about the remote build. 15 | * 16 | *

{@code
 17 |  * NOT_TRIGGERED ---+--->    QUEUED    --+-->    RUNNING    -----+----->         FINISHED
 18 |                              queueId           buildNumber                        result
 19 |                                                 & buildURL              (ABORTED | UNSTABLE | FAILURE | SUCCESS)
 20 |  *}
21 | * 22 | *

23 | * By default, the remote build status is NOT_TRIGGERED and the remote build result is NOT_BUILT. 24 | *

25 | * When the remote build is triggered, the remote job enters the queue (waiting list) 26 | * and the status of the remote build changes to QUEUED. In this moment the queueId is available. 27 | * The queueId can be used to request information about the remote job while it is waiting to be executed. 28 | *

29 | * When the remote job leaves the queue, the status changes to RUNNING. Then, the build number and the build URL 30 | * are available. The build URL can be used to request information about the remote job while it is being executed. 31 | *

32 | * When the remote job is finished, the status changes to FINISHED. Then, the remote build result is available. 33 | * 34 | */ 35 | public class RemoteBuildInfo implements Serializable 36 | { 37 | private static final long serialVersionUID = -5177308623227407314L; 38 | 39 | @CheckForNull 40 | private String queueId; 41 | 42 | @NonNull 43 | private int buildNumber; 44 | 45 | @CheckForNull 46 | private URL buildURL; 47 | 48 | @NonNull 49 | private RemoteBuildStatus status; 50 | 51 | @NonNull 52 | private Result result; 53 | 54 | 55 | public RemoteBuildInfo() 56 | { 57 | status = RemoteBuildStatus.NOT_TRIGGERED; 58 | result = Result.NOT_BUILT; 59 | } 60 | 61 | @CheckForNull 62 | public String getQueueId() { 63 | return queueId; 64 | } 65 | 66 | @NonNull 67 | public int getBuildNumber() 68 | { 69 | return buildNumber; 70 | } 71 | 72 | @CheckForNull 73 | public URL getBuildURL() 74 | { 75 | return buildURL; 76 | } 77 | 78 | @NonNull 79 | public RemoteBuildStatus getStatus() 80 | { 81 | return status; 82 | } 83 | 84 | @NonNull 85 | public Result getResult() 86 | { 87 | return result; 88 | } 89 | 90 | public void setQueueId(String queueId) { 91 | this.queueId = queueId; 92 | this.status = RemoteBuildStatus.QUEUED; 93 | } 94 | 95 | public void setBuildData(@NonNull int buildNumber, @Nullable URL buildURL) throws AbortException 96 | { 97 | if (buildURL == null) { 98 | throw new AbortException(String.format("Unexpected remote build status: %s", toString())); 99 | } 100 | this.buildNumber = buildNumber; 101 | this.buildURL = buildURL; 102 | this.status = RemoteBuildStatus.RUNNING; 103 | } 104 | 105 | public void setBuildStatus(RemoteBuildStatus status) 106 | { 107 | if (status == RemoteBuildStatus.FINISHED) { 108 | throw new IllegalArgumentException("It is not possible to set the status to finished without setting the build result. " 109 | + "Please use BuildInfo(Result result) or BuildInfo(String result) in order to set the status to finished."); 110 | } else { 111 | this.status = status; 112 | this.result = Result.NOT_BUILT; 113 | } 114 | } 115 | 116 | public void setBuildResult(Result result) 117 | { 118 | this.status = RemoteBuildStatus.FINISHED; 119 | this.result = result; 120 | } 121 | 122 | public void setBuildResult(String result) 123 | { 124 | this.status = RemoteBuildStatus.FINISHED; 125 | this.result = Result.fromString(result); 126 | } 127 | 128 | @NonNull 129 | @Override 130 | public String toString() 131 | { 132 | if (status == RemoteBuildStatus.FINISHED) return String.format("status=%s, result=%s", status.toString(), result.toString()); 133 | else return String.format("status=%s", status.toString()); 134 | } 135 | 136 | public boolean isNotTriggered() { 137 | return status == RemoteBuildStatus.NOT_TRIGGERED; 138 | } 139 | 140 | public boolean isQueued() { 141 | return status == RemoteBuildStatus.QUEUED; 142 | } 143 | 144 | public boolean isRunning() { 145 | return status == RemoteBuildStatus.RUNNING; 146 | } 147 | 148 | public boolean isFinished() { 149 | return status == RemoteBuildStatus.FINISHED; 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/ParameterizedRemoteTrigger/remoteJob/RemoteBuildInfoExporterAction.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.ParameterizedRemoteTrigger.remoteJob; 2 | 3 | import edu.umd.cs.findbugs.annotations.NonNull; 4 | 5 | import java.net.URL; 6 | import java.util.ArrayList; 7 | import java.util.LinkedHashSet; 8 | import java.util.List; 9 | import java.util.Set; 10 | 11 | import hudson.EnvVars; 12 | import hudson.model.AbstractBuild; 13 | import hudson.model.EnvironmentContributingAction; 14 | import hudson.model.Run; 15 | 16 | public class RemoteBuildInfoExporterAction implements EnvironmentContributingAction { 17 | 18 | public static final String JOB_NAME_VARIABLE = "LAST_TRIGGERED_JOB_NAME"; 19 | public static final String ALL_JOBS_NAME_VARIABLE = "TRIGGERED_JOB_NAMES"; 20 | public static final String BUILD_URL_VARIABLE_PREFIX = "TRIGGERED_BUILD_URL_"; 21 | public static final String BUILD_NUMBER_VARIABLE_PREFIX = "TRIGGERED_BUILD_NUMBER_"; 22 | public static final String ALL_BUILD_NUMBER_VARIABLE_PREFIX = "TRIGGERED_BUILD_NUMBERS_"; 23 | public static final String BUILD_RESULT_VARIABLE_PREFIX = "TRIGGERED_BUILD_RESULT_"; 24 | public static final String BUILD_RUN_COUNT_PREFIX = "TRIGGERED_BUILD_RUN_COUNT_"; 25 | public static final String RUN = "_RUN_"; 26 | 27 | private List builds; 28 | 29 | public RemoteBuildInfoExporterAction(Run parentBuild, BuildReference buildRef) { 30 | super(); 31 | 32 | this.builds = new ArrayList(); 33 | addBuildReferenceSafe(buildRef); 34 | } 35 | 36 | public static RemoteBuildInfoExporterAction addBuildInfoExporterAction(@NonNull Run parentBuild, String triggeredProjectName, int buildNumber, URL jobURL, RemoteBuildInfo buildInfo) { 37 | BuildReference reference = new BuildReference(triggeredProjectName, buildNumber, jobURL, buildInfo); 38 | 39 | RemoteBuildInfoExporterAction action; 40 | synchronized(parentBuild) { 41 | action = parentBuild.getAction(RemoteBuildInfoExporterAction.class); 42 | if (action == null) { 43 | action = new RemoteBuildInfoExporterAction(parentBuild, reference); 44 | parentBuild.addAction(action); 45 | } else { 46 | action.addBuildReference(reference); 47 | } 48 | } 49 | return action; 50 | } 51 | 52 | /** 53 | * Prevents duplicate build refs. The latest BuildReference wins (to reflect the latest Result). 54 | */ 55 | private void addBuildReferenceSafe(BuildReference buildRef) 56 | { 57 | synchronized (builds) { 58 | removeDuplicates(builds, buildRef); 59 | builds.add(buildRef); 60 | } 61 | } 62 | 63 | /** 64 | * Finds and removes duplicates of buildRef in the buildRefList based on the projectName and buildNumber (only). 65 | * @return true if duplicates found and removed, false if nothing found 66 | */ 67 | private boolean removeDuplicates(List buildRefList, BuildReference buildRef) { 68 | List duplicates = new ArrayList(); 69 | for(BuildReference build : buildRefList) { 70 | if(build.projectName.equals(buildRef.projectName) && build.buildNumber == buildRef.buildNumber) { 71 | duplicates.add(build); 72 | } 73 | } 74 | if(duplicates.size() > 0) { 75 | buildRefList.removeAll(duplicates); 76 | return true; 77 | } else { 78 | return false; 79 | } 80 | } 81 | 82 | public void addBuildReference(BuildReference buildRef) { 83 | addBuildReferenceSafe(buildRef); 84 | } 85 | 86 | public static class BuildReference { 87 | public final String projectName; 88 | public final int buildNumber; 89 | public final RemoteBuildInfo buildInfo; 90 | public final URL jobURL; 91 | 92 | public BuildReference(String projectName, int buildNumber, URL jobURL, RemoteBuildInfo buildInfo) { 93 | this.projectName = projectName; 94 | this.buildNumber = buildNumber; 95 | this.buildInfo = buildInfo; 96 | this.jobURL = jobURL; 97 | } 98 | } 99 | 100 | public String getIconFileName() { 101 | // TODO Auto-generated method stub 102 | return null; 103 | } 104 | 105 | public String getDisplayName() { 106 | // TODO Auto-generated method stub 107 | return null; 108 | } 109 | 110 | public String getUrlName() { 111 | // TODO Auto-generated method stub 112 | return null; 113 | } 114 | 115 | public void buildEnvVars(AbstractBuild build, EnvVars env) { 116 | for (String project : getProjectsWithBuilds()) { 117 | String sanatizedProjectName = sanitizeProjectName(project); 118 | List refs = getBuildRefs(project); 119 | 120 | env.put(ALL_BUILD_NUMBER_VARIABLE_PREFIX + sanatizedProjectName, getBuildNumbersString(refs, ",")); 121 | env.put(BUILD_RUN_COUNT_PREFIX + sanatizedProjectName, Integer.toString(refs.size())); 122 | for (BuildReference br : refs) { 123 | if (br.buildNumber != 0) { 124 | String tiggeredBuildRunResultKey = BUILD_RESULT_VARIABLE_PREFIX + sanatizedProjectName + RUN + Integer.toString(br.buildNumber); 125 | env.put(tiggeredBuildRunResultKey, br.buildInfo.getResult().toString()); 126 | } 127 | } 128 | BuildReference lastBuild = null; 129 | for (int i = (refs.size()); i > 0; i--) { 130 | if (refs.get(i - 1).buildNumber != 0) { 131 | lastBuild = refs.get(i - 1); 132 | break; 133 | } 134 | } 135 | if (lastBuild != null) { 136 | env.put(JOB_NAME_VARIABLE, lastBuild.projectName); 137 | env.put(BUILD_NUMBER_VARIABLE_PREFIX + sanatizedProjectName, Integer.toString(lastBuild.buildNumber)); 138 | env.put(BUILD_URL_VARIABLE_PREFIX + sanatizedProjectName, lastBuild.jobURL.toString()); 139 | env.put(BUILD_RESULT_VARIABLE_PREFIX + sanatizedProjectName, lastBuild.buildInfo.getResult().toString()); 140 | } 141 | } 142 | } 143 | 144 | public static String sanitizeProjectName(String project) 145 | { 146 | if(project == null) return null; 147 | return project.replaceAll("[^a-zA-Z0-9]+", "_"); 148 | } 149 | 150 | private List getBuildRefs(String project) { 151 | List refs = new ArrayList(); 152 | synchronized (builds) { 153 | for (BuildReference br : builds) { 154 | if (br.projectName.equals(project)) refs.add(br); 155 | } 156 | } 157 | return refs; 158 | } 159 | 160 | /** 161 | * Gets a string for all of the build numbers 162 | * 163 | * @param refs List of build references to process. 164 | * @param separator 165 | * @return String containing all the build numbers from refs, never null but 166 | * can be empty 167 | */ 168 | private String getBuildNumbersString(List refs, String separator) { 169 | StringBuilder buf = new StringBuilder(); 170 | boolean first = true; 171 | 172 | for (BuildReference s : refs) { 173 | if (s.buildNumber != 0) { 174 | if (first) { 175 | first = false; 176 | } else { 177 | buf.append(separator); 178 | } 179 | buf.append(s.buildNumber); 180 | } 181 | } 182 | return buf.toString(); 183 | } 184 | 185 | /** 186 | * Gets the unique set of project names that have a linked build.
187 | * The later triggered jobs are later in the list. E.g.
188 | * C, A, B -> C, A, B
189 | * C, A, B, A, C -> B, A, C
190 | * 191 | * @return Set of project names that have at least one build linked. 192 | */ 193 | protected Set getProjectsWithBuilds() { 194 | Set projects = new LinkedHashSet(); 195 | synchronized (builds) { 196 | for (BuildReference br : this.builds) { 197 | if (br.buildNumber != 0) { 198 | if(projects.contains(br.projectName)) projects.remove(br.projectName); //Move to the end 199 | projects.add(br.projectName); 200 | } 201 | } 202 | } 203 | return projects; 204 | } 205 | } -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/ParameterizedRemoteTrigger/remoteJob/RemoteBuildStatus.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.ParameterizedRemoteTrigger.remoteJob; 2 | 3 | /** 4 | * This class implements the build status of a remote build. 5 | * 6 | *

{@code
 7 |  * NOT_TRIGGERED --+--> QUEUED --+--> RUNNING --+--> FINISHED
 8 |                           |                              |
 9 |                           |                              |
10 |                           +-------> Cancelled <----------+
11 |  *}
12 | * 13 | *

14 | * By default, the remote build status is NOT_TRIGGERED. 15 | *

16 | * When the remote build is triggered, the remote job enters the queue (waiting list) 17 | * and the status of the remote build changes to QUEUED. 18 | *

19 | * When the remote job leaves the queue, the status changes to RUNNING. 20 | *

21 | * When the remote job is finished, the status changes to FINISHED. This is the final status. 22 | * 23 | * In addition, at the status QUEUED and FINISHED, a remote build can be cancelled, 24 | * in this case an AbortException is thrown. 25 | */ 26 | public enum RemoteBuildStatus 27 | { 28 | /** 29 | * The remote job was not triggered and it did not enter the queue. 30 | */ 31 | NOT_TRIGGERED("NOT_TRIGGERED"), 32 | 33 | /** 34 | * The remote job was triggered and it did enter the queue. 35 | */ 36 | QUEUED("QUEUED"), 37 | 38 | /** 39 | * The remote job left the queue and it is running currently. 40 | */ 41 | RUNNING("RUNNING"), 42 | 43 | /** 44 | * The remote build is finished. 45 | */ 46 | FINISHED("FINISHED"); 47 | 48 | 49 | private final String id; 50 | 51 | 52 | private RemoteBuildStatus(String id) { 53 | this.id = id; 54 | } 55 | 56 | @Override 57 | public String toString() { 58 | return id; 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/ParameterizedRemoteTrigger/utils/Base64Utils.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.ParameterizedRemoteTrigger.utils; 2 | 3 | import static org.apache.commons.lang.StringUtils.isEmpty; 4 | 5 | import java.io.IOException; 6 | 7 | import edu.umd.cs.findbugs.annotations.NonNull; 8 | import java.io.UnsupportedEncodingException; 9 | 10 | import org.apache.commons.codec.binary.Base64; 11 | import org.jenkinsci.plugins.ParameterizedRemoteTrigger.BuildContext; 12 | 13 | public class Base64Utils 14 | { 15 | 16 | public static final String AUTHTYPE_BASIC = "Basic"; 17 | 18 | public static String encode(String input) throws UnsupportedEncodingException 19 | { 20 | byte[] encoded = Base64.encodeBase64(input.getBytes("UTF-8")); 21 | return new String(encoded, "UTF-8"); 22 | } 23 | 24 | /** 25 | * Creates the value for an Authorization header consisting of:
26 | * "authType base64Encoded(user:password)"
27 | * e.g. "Basic zhwef9tz33ergwerg4394zh370345zh==" 28 | * 29 | * @param authType 30 | * the authorization type. 31 | * @param user 32 | * the user name. 33 | * @param password 34 | * the user password. 35 | * @param context 36 | * the context of this Builder/BuildStep. 37 | * @param applyMacro 38 | * boolean to control if macro replacements occur 39 | * @return the base64 encoded authorization. 40 | * @throws IOException 41 | * if there is a failure while replacing token macros, or 42 | * if there is a failure while encoding user:password. 43 | */ 44 | @NonNull 45 | public static String generateAuthorizationHeaderValue(String authType, String user, String password, 46 | BuildContext context, boolean applyMacro) throws IOException 47 | { 48 | if (isEmpty(user)) throw new IllegalArgumentException("user null or empty"); 49 | if (password == null) throw new IllegalArgumentException("password null"); // is empty password allowed for Basic Auth? 50 | String authTypeKey = getAuthType(authType); 51 | String tuple = user + ":" + password; 52 | if (applyMacro) { 53 | tuple = TokenMacroUtils.applyTokenMacroReplacements(tuple, context); 54 | } 55 | String encodedTuple = Base64Utils.encode(tuple); 56 | return authTypeKey + " " + encodedTuple; 57 | } 58 | 59 | @NonNull 60 | private static String getAuthType(String authType) 61 | { 62 | if ("Basic".equalsIgnoreCase(authType)) return "Basic"; 63 | throw new IllegalArgumentException("AuthType wrong or not supported yet: " + authType); 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/ParameterizedRemoteTrigger/utils/DropCachePeriodicWork.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.ParameterizedRemoteTrigger.utils; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | import java.util.concurrent.TimeUnit; 6 | import java.util.concurrent.locks.Lock; 7 | import java.util.concurrent.locks.ReentrantLock; 8 | import java.util.logging.Level; 9 | import java.util.logging.Logger; 10 | 11 | import org.jenkinsci.plugins.ParameterizedRemoteTrigger.JenkinsCrumb; 12 | 13 | import hudson.Extension; 14 | import hudson.model.PeriodicWork; 15 | import net.sf.json.JSONObject; 16 | 17 | @Extension 18 | public class DropCachePeriodicWork extends PeriodicWork { 19 | 20 | private static Map crumbMap = new HashMap<>(); 21 | private static Map jobInfoMap = new HashMap<>(); 22 | 23 | private static Logger logger = Logger.getLogger(DropCachePeriodicWork.class.getName()); 24 | private static Lock jobInfoLock = new ReentrantLock(); 25 | private static Lock crumbLock = new ReentrantLock(); 26 | 27 | @Override 28 | public long getRecurrencePeriod() { 29 | return TimeUnit.MINUTES.toMillis(10); 30 | } 31 | 32 | public static JenkinsCrumb safePutCrumb(String key, JenkinsCrumb jenkinsCrumb, boolean isCacheEnable) { 33 | if (!isCacheEnable) 34 | return jenkinsCrumb; 35 | try { 36 | crumbLock.lock(); 37 | crumbMap.put(key, jenkinsCrumb); 38 | return jenkinsCrumb; 39 | } finally { 40 | crumbLock.unlock(); 41 | } 42 | } 43 | 44 | public static JenkinsCrumb safeGetCrumb(String key, boolean isCacheEnable) { 45 | if (!isCacheEnable) 46 | return null; 47 | try { 48 | crumbLock.lock(); 49 | if (crumbMap.containsKey(key)) { 50 | return crumbMap.get(key); 51 | } else { 52 | return null; 53 | } 54 | } finally { 55 | crumbLock.unlock(); 56 | } 57 | } 58 | 59 | public static JSONObject safePutJobInfo(String key, JSONObject jobInfo, boolean isCacheEnable) { 60 | if (!isCacheEnable) 61 | return jobInfo; 62 | try { 63 | jobInfoLock.lock(); 64 | jobInfoMap.put(key, jobInfo); 65 | return jobInfo; 66 | } finally { 67 | jobInfoLock.unlock(); 68 | } 69 | } 70 | 71 | public static JSONObject safeGetJobInfo(String key, boolean isCacheEnable) { 72 | if (!isCacheEnable) 73 | return null; 74 | try { 75 | jobInfoLock.lock(); 76 | if (jobInfoMap.containsKey(key)) { 77 | return jobInfoMap.get(key); 78 | } else { 79 | return null; 80 | } 81 | } finally { 82 | jobInfoLock.unlock(); 83 | } 84 | } 85 | 86 | @Override 87 | protected void doRun() throws Exception { 88 | logger.log(Level.INFO, "begin schedule clean..."); 89 | 90 | try { 91 | crumbLock.lock(); 92 | crumbMap.clear(); 93 | } catch (Exception e) { 94 | logger.log(Level.WARNING, "Fail to clear crumb cache", e); 95 | } finally { 96 | crumbLock.unlock(); 97 | } 98 | 99 | try { 100 | jobInfoLock.lock(); 101 | jobInfoMap.clear(); 102 | } catch (Exception e) { 103 | logger.log(Level.WARNING, "Fail to clear job info cache", e); 104 | } finally { 105 | jobInfoLock.unlock(); 106 | } 107 | 108 | logger.log(Level.INFO, "end schedule clean..."); 109 | } 110 | 111 | } 112 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/ParameterizedRemoteTrigger/utils/FormValidationUtils.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.ParameterizedRemoteTrigger.utils; 2 | 3 | import static org.apache.commons.lang.StringUtils.isEmpty; 4 | import static org.apache.commons.lang.StringUtils.trimToNull; 5 | 6 | import java.net.MalformedURLException; 7 | import java.net.URL; 8 | import java.util.Arrays; 9 | 10 | import org.kohsuke.accmod.Restricted; 11 | import org.kohsuke.accmod.restrictions.NoExternalUse; 12 | 13 | import hudson.util.FormValidation; 14 | 15 | @Restricted(NoExternalUse.class) 16 | public class FormValidationUtils 17 | { 18 | 19 | public static enum AffectedField { 20 | JOB_NAME_OR_URL, REMOTE_JENKINS_URL, REMOTE_JENKINS_NAME 21 | } 22 | 23 | public static class RemoteURLCombinationsResult { 24 | 25 | public final FormValidation formValidation; 26 | public final AffectedField[] affectedFields; 27 | 28 | public RemoteURLCombinationsResult(FormValidation formValidation, AffectedField...affectedFields) { 29 | this.formValidation = formValidation; 30 | this.affectedFields = affectedFields; 31 | } 32 | 33 | public boolean isAffected(AffectedField field) { 34 | return Arrays.asList(affectedFields).contains(field); 35 | } 36 | 37 | public static RemoteURLCombinationsResult OK() 38 | { 39 | return new RemoteURLCombinationsResult(FormValidation.ok(), AffectedField.values()); 40 | } 41 | 42 | } 43 | 44 | public static RemoteURLCombinationsResult checkRemoteURLCombinations(String remoteJenkinsUrl, String remoteJenkinsName, String jobNameOrUrl) { 45 | remoteJenkinsUrl = trimToNull(remoteJenkinsUrl); 46 | remoteJenkinsName = trimToNull(remoteJenkinsName); 47 | jobNameOrUrl = trimToNull(jobNameOrUrl); 48 | boolean remoteUrl_setAndValidUrl = isEmpty(remoteJenkinsUrl) ? false : isURL(remoteJenkinsUrl); 49 | boolean remoteName_setAndValid = !isEmpty(remoteJenkinsName); 50 | boolean job_setAndValidUrl = isEmpty(jobNameOrUrl) ? false : isURL(jobNameOrUrl); 51 | boolean job_setAndNoUrl = isEmpty(jobNameOrUrl) ? false : !isURL(jobNameOrUrl); 52 | boolean job_containsVariable = isEmpty(jobNameOrUrl) ? false : jobNameOrUrl.indexOf("$") >= 0; 53 | final String TEXT_WARNING_JOB_VARIABLE = "You are using a variable in the 'Remote Job Name or URL' ('job') field. You have to make sure the value at runtime results in the full job URL"; 54 | final String TEXT_ERROR_NO_URL_AT_ALL = "You have to configure either 'Select a remote host' ('remoteJenkinsName'), 'Override remote host URL' ('remoteJenkinsUrl') or specify a full job URL 'Remote Job Name or URL' ('job')"; 55 | 56 | if(isEmpty(jobNameOrUrl)) { 57 | return new RemoteURLCombinationsResult( 58 | FormValidation.error("'Remote Job Name or URL' ('job') not specified"), 59 | AffectedField.JOB_NAME_OR_URL); 60 | } else if(!isEmpty(remoteJenkinsUrl) && !isURL(remoteJenkinsUrl)) { 61 | return new RemoteURLCombinationsResult( 62 | FormValidation.error("Invalid URL in 'Override remote host URL' ('remoteJenkinsUrl')"), 63 | AffectedField.REMOTE_JENKINS_URL); 64 | } else if(!remoteUrl_setAndValidUrl && !remoteName_setAndValid && !job_setAndValidUrl) { 65 | //Root URL or full job URL not specified at all 66 | if(job_containsVariable) { 67 | return new RemoteURLCombinationsResult(FormValidation.warning(TEXT_WARNING_JOB_VARIABLE), AffectedField.JOB_NAME_OR_URL); 68 | } else { 69 | return new RemoteURLCombinationsResult(FormValidation.error(TEXT_ERROR_NO_URL_AT_ALL), 70 | AffectedField.JOB_NAME_OR_URL, AffectedField.REMOTE_JENKINS_NAME, AffectedField.REMOTE_JENKINS_URL); 71 | } 72 | } else if(job_setAndValidUrl) { 73 | return RemoteURLCombinationsResult.OK(); 74 | } else if(remoteUrl_setAndValidUrl && job_setAndNoUrl) { 75 | if(job_containsVariable) { 76 | return new RemoteURLCombinationsResult(FormValidation.warning(TEXT_WARNING_JOB_VARIABLE), AffectedField.JOB_NAME_OR_URL); 77 | } else { 78 | return RemoteURLCombinationsResult.OK(); 79 | } 80 | } else if(remoteName_setAndValid && job_setAndNoUrl) { 81 | if(job_containsVariable) { 82 | return new RemoteURLCombinationsResult(FormValidation.warning(TEXT_WARNING_JOB_VARIABLE), AffectedField.JOB_NAME_OR_URL); 83 | } else { 84 | return RemoteURLCombinationsResult.OK(); 85 | } 86 | } else { 87 | return new RemoteURLCombinationsResult(FormValidation.error(TEXT_ERROR_NO_URL_AT_ALL), 88 | AffectedField.JOB_NAME_OR_URL, AffectedField.REMOTE_JENKINS_NAME, AffectedField.REMOTE_JENKINS_URL); 89 | } 90 | } 91 | 92 | /** 93 | * Checks if a string is a valid http/https URL. 94 | * 95 | * @param string 96 | * the url to check. 97 | * @return true if parameter is a valid http/https URL. 98 | */ 99 | public static boolean isURL(String string) { 100 | if(isEmpty(trimToNull(string))) return false; 101 | String stringLower = string.toLowerCase(); 102 | if(stringLower.startsWith("http://") || stringLower.startsWith("https://")) { 103 | if(stringLower.indexOf("://") >= stringLower.length()-3) { 104 | return false; //URL ends after protocol 105 | } 106 | if(stringLower.indexOf("$") >= 0) { 107 | return false; //We interpret $ in URLs as variables which need to be replaced. TODO: What about URI standard which allows $? 108 | } 109 | try { 110 | new URL(string); 111 | return true; 112 | } 113 | catch (MalformedURLException e) { 114 | return false; 115 | } 116 | } else { 117 | return false; 118 | } 119 | } 120 | 121 | } 122 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/ParameterizedRemoteTrigger/utils/NaiveTrustManager.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.ParameterizedRemoteTrigger.utils; 2 | 3 | import javax.net.ssl.*; 4 | import java.security.cert.X509Certificate; 5 | 6 | // Trust every server 7 | public class NaiveTrustManager implements X509TrustManager { 8 | @Override 9 | public void checkClientTrusted(X509Certificate[] arg0, String arg1) {} 10 | 11 | @Override 12 | public void checkServerTrusted(X509Certificate[] arg0, String arg1) {} 13 | 14 | @Override 15 | public X509Certificate[] getAcceptedIssuers() { 16 | return null; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/ParameterizedRemoteTrigger/utils/OtelUtils.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.ParameterizedRemoteTrigger.utils; 2 | 3 | import hudson.Plugin; 4 | import hudson.PluginWrapper; 5 | import hudson.model.Run; 6 | import io.jenkins.plugins.opentelemetry.job.OtelTraceService; 7 | import io.opentelemetry.api.trace.Span; 8 | import io.opentelemetry.context.Context; 9 | import jenkins.model.Jenkins; 10 | import org.jenkinsci.plugins.workflow.graph.FlowNode; 11 | import org.jenkinsci.plugins.workflow.steps.StepContext; 12 | import edu.umd.cs.findbugs.annotations.NonNull; 13 | 14 | import java.util.Optional; 15 | 16 | public class OtelUtils { 17 | 18 | private static String TRACE_PARENT_VERSION = "00"; 19 | private static String TRACE_PARENT_TRACE_FLAG = "01"; 20 | 21 | public static String getTraceParent() { 22 | return Optional.ofNullable(Span.fromContextOrNull(Context.current())) 23 | .map(OtelUtils::genTraceParent) 24 | .orElse(null); 25 | 26 | } 27 | 28 | 29 | public static AutoCloseable activeSpanIfAvailable(StepContext stepContext) { 30 | try { 31 | FlowNode flowNode = stepContext.get(FlowNode.class); 32 | Run run = stepContext.get(Run.class); 33 | return Optional.ofNullable(Jenkins.get().getExtensionList(OtelTraceService.class)) 34 | .filter(list -> list.size()>0) 35 | .map(list -> list.get(0)) 36 | .map(otelTraceServices -> otelTraceServices.getSpan(run, flowNode)) 37 | .map(Span::makeCurrent) 38 | .map(AutoCloseable.class::cast) 39 | .orElseGet(OtelUtils::noop); 40 | } catch (Exception e) { 41 | throw new RuntimeException(e); 42 | } 43 | } 44 | 45 | public static AutoCloseable activeSpanIfAvailable(Run run) { 46 | return Optional.ofNullable(Jenkins.get().getExtensionList(OtelTraceService.class)) 47 | .filter(list -> list.size()>0) 48 | .map(list -> list.get(0)) 49 | .map(otelTraceServices -> otelTraceServices.getSpan(run)) 50 | .map(Span::makeCurrent) 51 | .map(AutoCloseable.class::cast) 52 | .orElseGet(OtelUtils::noop); 53 | } 54 | 55 | @NonNull 56 | public static AutoCloseable noop() { 57 | return () -> { 58 | }; 59 | } 60 | 61 | @NonNull 62 | public static boolean isOpenTelemetryAvailable() { 63 | return Optional.ofNullable(Jenkins.get().getPlugin("opentelemetry")) 64 | .map(Plugin::getWrapper) 65 | .map(PluginWrapper::isActive) 66 | .orElse(false); 67 | } 68 | 69 | @NonNull 70 | private static String genTraceParent(Span span) { 71 | return TRACE_PARENT_VERSION + "-" + span.getSpanContext().getTraceId() + "-" + span.getSpanContext().getSpanId() + "-" + TRACE_PARENT_TRACE_FLAG; 72 | } 73 | 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/ParameterizedRemoteTrigger/utils/RestUtils.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.ParameterizedRemoteTrigger.utils; 2 | 3 | import java.io.IOException; 4 | import java.util.logging.Logger; 5 | 6 | import org.jenkinsci.plugins.ParameterizedRemoteTrigger.BuildContext; 7 | import org.jenkinsci.plugins.ParameterizedRemoteTrigger.ConnectionResponse; 8 | import org.jenkinsci.plugins.ParameterizedRemoteTrigger.RemoteBuildConfiguration; 9 | import org.jenkinsci.plugins.ParameterizedRemoteTrigger.exceptions.ExceedRetryLimitException; 10 | import org.jenkinsci.plugins.ParameterizedRemoteTrigger.pipeline.Handle; 11 | import org.jenkinsci.plugins.ParameterizedRemoteTrigger.remoteJob.RemoteBuildInfo; 12 | import org.jenkinsci.plugins.ParameterizedRemoteTrigger.remoteJob.RemoteBuildStatus; 13 | 14 | /* 15 | * Going to migrate all rest APIs to here 16 | * */ 17 | public class RestUtils { 18 | 19 | private static Logger logger = Logger.getLogger(RestUtils.class.getName()); 20 | 21 | public static ConnectionResponse cancelQueueItem(String rootUrl, Handle handle, BuildContext context, 22 | RemoteBuildConfiguration remoteConfig) throws IOException, InterruptedException { 23 | 24 | String cancelQueueUrl = String.format("%s/queue/cancelItem?id=%s", rootUrl, handle.getQueueId()); 25 | ConnectionResponse resp = null; 26 | try { 27 | resp = HttpHelper.tryPost(cancelQueueUrl, context, null, remoteConfig.getHttpPostReadTimeout(), 28 | remoteConfig.getPollInterval(RemoteBuildStatus.QUEUED) * 2, 0, 29 | remoteConfig.getAuth2(), remoteConfig.getLock(cancelQueueUrl), remoteConfig.isUseCrumbCache()); 30 | } catch (ExceedRetryLimitException e) { 31 | // Due to https://issues.jenkins-ci.org/browse/JENKINS-21311, we can't tell 32 | // whether the action was succeed, 33 | // Only try once and treat it as success 34 | logger.warning("Canceled queue item and not sure if it was succeed"); 35 | } 36 | context.logger.println(String.format("Remote Queued Items:%s was canceled!", handle.getQueueId())); 37 | return resp; 38 | } 39 | 40 | public static ConnectionResponse stopRemoteJob(Handle handle, BuildContext context, 41 | RemoteBuildConfiguration remoteConfig) throws IOException, InterruptedException { 42 | 43 | RemoteBuildInfo buildInfo = handle.getBuildInfo(); 44 | String stopJobUrl = String.format("%sstop", buildInfo.getBuildURL()); 45 | ConnectionResponse resp = HttpHelper.tryPost(stopJobUrl, context, null, remoteConfig.getHttpPostReadTimeout(), 46 | remoteConfig.getPollInterval(buildInfo.getStatus()), remoteConfig.getConnectionRetryLimit(), 47 | remoteConfig.getAuth2(), remoteConfig.getLock(stopJobUrl), remoteConfig.isUseCrumbCache()); 48 | context.logger.println(String.format("Remote Job:%s was aborted!", buildInfo.getBuildURL())); 49 | return resp; 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/ParameterizedRemoteTrigger/utils/StringTools.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.ParameterizedRemoteTrigger.utils; 2 | 3 | public class StringTools 4 | { 5 | 6 | /** 7 | * System specific new line character/string 8 | */ 9 | public static final String NL = getSystemLineSeparator(); 10 | 11 | /** 12 | * Unix/Linux specific new line character '\n' 13 | */ 14 | public static final String NL_UNIX = "\n"; 15 | 16 | private static String getSystemLineSeparator() { 17 | String newLine = System.getProperty("line.separator"); 18 | if(newLine == null || newLine.length() <= 0) newLine = "\n"; 19 | return newLine; 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/org/jenkinsci/plugins/ParameterizedRemoteTrigger/utils/TokenMacroUtils.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.ParameterizedRemoteTrigger.utils; 2 | 3 | import java.io.IOException; 4 | import java.util.LinkedHashMap; 5 | import java.util.Map; 6 | 7 | import org.jenkinsci.plugins.ParameterizedRemoteTrigger.BasicBuildContext; 8 | import org.jenkinsci.plugins.tokenmacro.MacroEvaluationException; 9 | import org.jenkinsci.plugins.tokenmacro.TokenMacro; 10 | 11 | public class TokenMacroUtils { 12 | 13 | public static String applyTokenMacroReplacements(String input, BasicBuildContext context) throws IOException { 14 | try { 15 | if (isUseTokenMacro(context)) { 16 | return TokenMacro.expandAll(context.run, context.workspace, context.listener, input); 17 | } 18 | } catch (MacroEvaluationException e) { 19 | throw new IOException(e); 20 | } catch (InterruptedException e) { 21 | throw new IOException(e); 22 | } 23 | return input; 24 | } 25 | 26 | public static Map applyTokenMacroReplacements(Map input, BasicBuildContext context) 27 | throws IOException { 28 | 29 | Map output = new LinkedHashMap<>(); 30 | for (Map.Entry entry : input.entrySet()) { 31 | output.put(entry.getKey(), applyTokenMacroReplacements(entry.getValue(), context)); 32 | } 33 | return output; 34 | } 35 | 36 | public static boolean isUseTokenMacro(BasicBuildContext context) { 37 | return context != null && context.run != null && context.workspace != null && context.listener != null; 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /src/main/resources/index.jelly: -------------------------------------------------------------------------------- 1 | 2 |

3 | This plugin triggers a job on a remote Jenkins host 4 |
5 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ParameterizedRemoteTrigger/Auth/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 |
27 | 28 |
29 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ParameterizedRemoteTrigger/RemoteBuildConfiguration/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 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 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ParameterizedRemoteTrigger/RemoteBuildConfiguration/global.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ParameterizedRemoteTrigger/RemoteBuildConfiguration/help-auth2.html: -------------------------------------------------------------------------------- 1 |
2 | Using this parameter you can override the authentication used to connect to the selected remote Jenkins. 3 |
    4 |
  • Don't Override
    5 | The authentication configured in the (global) settings of the selected 'remote host' is used. 6 |
  • 7 |
  • Token Authentication
    8 | The specified user id and Jenkins API token is used. 9 |
  • 10 |
  • Credentials Authentication
    11 | The specified Jenkins Credentials are used. This can be either user/password or user/API Token. 12 |
  • 13 |
  • No Authentication
    14 | No Authorization header will be sent, independent of the global 'remote host' settings. 15 |
  • 16 |
  • Bearer Authentication
    17 | The bearer token is used. 18 |
  • 19 |
20 | 21 | Note: Jenkins API Tokens are recommended since, if stolen, they allow access only to a specific Jenkins 22 | while user and password typically provide access to many systems. 23 | 24 |
25 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ParameterizedRemoteTrigger/RemoteBuildConfiguration/help-disabled.html: -------------------------------------------------------------------------------- 1 |
2 | Set this field to disable the job step instead of removing it from job configuration. 3 |
4 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ParameterizedRemoteTrigger/RemoteBuildConfiguration/help-enhancedLogging.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | Enable Enhanced Logging 4 |
5 | If this option is enabled, the console output of the remote job is also logged. 6 |
7 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ParameterizedRemoteTrigger/RemoteBuildConfiguration/help-job.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | Remote Job Name or full URL. 4 |
5 | The name or URL of the job on the remote Jenkins host which you would like to trigger. If the full job URL is specified the URL of the remote Jenkins host configured above will be ignored. 6 |
7 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ParameterizedRemoteTrigger/RemoteBuildConfiguration/help-maxConn.html: -------------------------------------------------------------------------------- 1 |
2 | The max concurrent connections to the remote host, default is 1, max is 5. It'll be 5 even if you set it greater than 5. 3 | Note: Set this field with caution, too many concurrent requests will not only fail your local jobs,
4 | but also block the remote server. 5 | 6 |
7 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ParameterizedRemoteTrigger/RemoteBuildConfiguration/help-parameters2.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | Job Parameters 4 |
5 | Parameters which will be used when triggering the remote job. 6 |
7 | If no parameters are needed, then just leave this blank. 8 |
    9 |
  • Map parameters
    10 | This is the recommended type. It allows you to provide parameters with key/value and supports multi-line parameters. 11 |
  • 12 |
  • String parameters (legacy)
    13 | This type allows to describe parameters within a big string like in versions 3.1.5 and lower. It does not support multi-line parameters. 14 |
  • 15 |
  • File parameters (legacy)
    16 | This type allows to describe parameters within a file like in versions 3.1.5 and lower. It does not support multi-line parameters. 17 |
  • 18 |
19 |
20 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ParameterizedRemoteTrigger/RemoteBuildConfiguration/help-remoteJenkinsUrl.html: -------------------------------------------------------------------------------- 1 |
2 | It is possible to override the Remote Jenkins URL for each Job separately. 3 |
4 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ParameterizedRemoteTrigger/RemoteBuildConfiguration/help-shouldNotFailBuild.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | Do Not Fail If Remote Fails 4 |
5 | If this option is enabled, the build will not fail even if the remote build fails. 6 |
-------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ParameterizedRemoteTrigger/RemoteBuildConfiguration/help-token.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | Remote Job Token 4 |
5 | Security token which is defined on the job of the remote Jenkins host. 6 |
7 | If no job token is needed to trigger this job, then just leave it blank 8 |
-------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ParameterizedRemoteTrigger/RemoteBuildConfiguration/help-trustAllCertificates.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | Trust all certificates 4 |
5 | 6 |

7 | It is possible to override/rewrite the 'Trust all certificate'-setting for each Job separately. 8 | Setting this checkbox to 'true' will result in accepting all certificates for the given Job. 9 |

10 | 11 |
12 | If your remote Jenkins host has a 13 | 14 | self-signed certificate 15 | 16 | or its certificate is not trusted, you may want to enable this option. 17 | It will accept untrusted certificates for the given host. 18 |
19 | 20 |

This is unsafe and should only be used for testing or if you trust the host.

21 |
-------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ParameterizedRemoteTrigger/RemoteBuildConfiguration/help-useCrumbCache.html: -------------------------------------------------------------------------------- 1 |
2 | Set this field to enable cache of the crumb of remote server.
3 | It'll be more efficient for the local job execution & more stable for remote server when massive concurrent jobs are triggered.
4 | This cache will be cleared every 10 minutes. 5 |
6 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ParameterizedRemoteTrigger/RemoteBuildConfiguration/help-useJobInfoCache.html: -------------------------------------------------------------------------------- 1 |
2 | Set this field to enable cache of the job info of remote server.
3 | It'll be more efficient for the local job execution & more stable for remote server when massive concurrent jobs are triggered.
4 | This cache will be cleared every 10 minutes. 5 |
6 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ParameterizedRemoteTrigger/RemoteJenkinsServer/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 |
31 |
32 | 33 | 34 |
35 |
36 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ParameterizedRemoteTrigger/RemoteJenkinsServer/help-address.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | Remote Jenkins Address 4 |
5 | Address (and optional port number) for the remote Jenkins host. 6 |
7 | i.e.: http://project.myserver.com:8888/jenkins 8 |

9 | Please note that the protocol (http, https, etc) is also required. Otherwise, the validation will fail. 10 |
-------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ParameterizedRemoteTrigger/RemoteJenkinsServer/help-apiToken.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | API Token 4 |
5 | This API token from the remote host that will be used for authentication. 6 |
7 | See the 8 | Jenkins wiki 9 | 10 | for more details. 11 |
12 | 13 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ParameterizedRemoteTrigger/RemoteJenkinsServer/help-auth2.html: -------------------------------------------------------------------------------- 1 |
2 | Using this parameter you can configure the authentication used to connect to the selected remote Jenkins. 3 |
    4 |
  • No Authentication
    5 | No Authorization header will be sent. 6 |
  • 7 |
  • Token Authentication
    8 | The specified user id and Jenkins API token is used. 9 |
  • 10 |
  • Credentials Authentication
    11 | The specified Jenkins Credentials are used. This can be either user/password or user/API Token. 12 |
  • 13 |
  • Bearer Authentication
    14 | The bearer token is used. 15 |
  • 16 |
17 | 18 | Note: Jenkins API Tokens are recommended since, if stolen, they allow access only to a specific Jenkins 19 | while user and password typically provide access to many systems. 20 | 21 |
22 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ParameterizedRemoteTrigger/RemoteJenkinsServer/help-displayName.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | Display Name 4 |
5 | If a name is given, this will be visible when configuring your job. 6 |
7 | If none is given, the entered address will be displayed instead 8 |
-------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ParameterizedRemoteTrigger/RemoteJenkinsServer/help-hasBuildTokenRootSupport.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | Build Token Root 4 |
5 | If your remote Jenkins host has the 6 | 7 | "Build Token Root" plugin" 8 | 9 | installed, then you will want to enable this option. 10 |
-------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ParameterizedRemoteTrigger/RemoteJenkinsServer/help-trustAllCertificates.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | Trust all certificates 4 |
5 | 6 |
7 | If your remote Jenkins host has a 8 | 9 | self-signed certificate 10 | 11 | or its certificate is not trusted, you may want to enable this option. 12 | It will accept untrusted certificates for the given host. 13 |
14 | 15 |

This is unsafe and should only be used for testing or if you trust the host.

16 |
-------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ParameterizedRemoteTrigger/RemoteJenkinsServer/help-username.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | Remote Username 4 |
5 | The username that will be utilized when opening a connection to the remote host. 6 |
7 | 8 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ParameterizedRemoteTrigger/RemoteJenkinsServer/help.html: -------------------------------------------------------------------------------- 1 |
2 | The name of the remote Jenkins as configured in the Jenkins global configuration (Manage Jenkins > Configure System > Parameterized Remote Trigger Configuration > Remote Hosts). 3 |
4 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ParameterizedRemoteTrigger/auth2/BearerTokenAuth/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ParameterizedRemoteTrigger/auth2/CredentialsAuth/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ParameterizedRemoteTrigger/auth2/TokenAuth/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ParameterizedRemoteTrigger/parameters2/FileParameters/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ParameterizedRemoteTrigger/parameters2/MapParameter/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ParameterizedRemoteTrigger/parameters2/MapParameters/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 |
10 |
11 |
12 |
13 | 14 |
15 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ParameterizedRemoteTrigger/parameters2/StringParameters/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ParameterizedRemoteTrigger/pipeline/RemoteBuildPipelineStep/config.jelly: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 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 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ParameterizedRemoteTrigger/pipeline/RemoteBuildPipelineStep/help-auth.html: -------------------------------------------------------------------------------- 1 |
2 | Using this parameter you can override the authentication used to connect to the selected remote Jenkins.
3 |
    4 |
  • Don't Override
    5 | The authentication configured in the (global) settings of the selected 'remote host' is used. 6 |
  • 7 |
  • Token Authentication
    8 | The specified user id and Jenkins API token is used. 9 |
  • 10 |
  • Credentials Authentication
    11 | The specified Jenkins Credentials are used. This can be either user/password or user/API Token. 12 |
  • 13 |
  • No Authentication
    14 | No Authorization header will be sent, independent of the global 'remote host' settings. 15 |
  • 16 |
17 | 18 | Note: Jenkins API Tokens are recommended since, if stolen, they allow access only to a specific Jenkins 19 | while user and password typically provide access to many systems. 20 | 21 |
22 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ParameterizedRemoteTrigger/pipeline/RemoteBuildPipelineStep/help-blockBuildUntilComplete.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | Wait/Block Until Remote Build Complete 4 |
5 | If enabled the remote job is called synchronously and the plugin waits until the remote job finished.
6 | If disabled the plugin triggers the remote job and returns.
7 |
8 | In both cases a handle is returned for further tracking the remote job or getting the results (see plugin main help page). 9 |

10 | mandatory: no
11 | default: true 12 |

13 |
14 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ParameterizedRemoteTrigger/pipeline/RemoteBuildPipelineStep/help-disabled.html: -------------------------------------------------------------------------------- 1 |
2 | Set this field to disable the job step instead of removing it from job configuration. 3 |
4 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ParameterizedRemoteTrigger/pipeline/RemoteBuildPipelineStep/help-enhancedLogging.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | Enable Enhanced Logging 4 |
5 | If this option is enabled, the console output of the remote job is also logged. 6 |

7 | mandatory: no
8 | default: false 9 |

10 |
11 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ParameterizedRemoteTrigger/pipeline/RemoteBuildPipelineStep/help-job.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | Remote Job Name or full URL. 4 |
5 | The name or URL of the job on the remote Jenkins host which you would like to trigger. If the full job URL is specified the URL of the remote Jenkins host configured above will be ignored. 6 |

7 | mandatory: yes
8 |

9 |
10 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ParameterizedRemoteTrigger/pipeline/RemoteBuildPipelineStep/help-maxConn.html: -------------------------------------------------------------------------------- 1 |
2 | The max concurrent connections to the remote host, default is 1, max is 5. It'll be 5 even if you set it greater than 5. 3 | Note: Set this field with caution, too many concurrent requests will not only fail your local jobs,
4 | but also block the remote server. 5 | 6 |
7 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ParameterizedRemoteTrigger/pipeline/RemoteBuildPipelineStep/help-parameters.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | Job Parameters 4 |
5 | Parameters which will be used when triggering the remote job. 6 |
7 | If no parameters are needed, then just leave this blank. 8 |
9 | In case of a String, the plugin uses String/File parameters depending on the parameter's content. 10 | 11 |
    12 |
  • Map parameters
    13 |

    Map<String, Object>

    14 |

    This is the recommended type. It allows you to provide parameters with key/value and supports multi-line parameters.

    15 |
  • 16 |
  • String parameters (legacy)
    17 |

    String

    18 |

    This type allows to describe parameters within a big string like in versions 3.1.5 and lower. It does not support multi-line parameters.

    19 |
  • 20 |
  • File parameters (legacy)
    21 |

    String

    22 | This type allows to describe parameters within a file like in versions 3.1.5 and lower. It does not support multi-line parameters. 23 |
  • 24 |
25 |
26 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ParameterizedRemoteTrigger/pipeline/RemoteBuildPipelineStep/help-pollInterval.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | Polling Interval 4 |
5 | The plugin identifies the status of the remote build by polling. Here you can specify how often the plugin shall poll the remote status.
6 | Be aware that polling too often might cause an increased load on the remote Jenkins. 7 |

8 | mandatory: no
9 | default: 10 10 |

11 |
12 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ParameterizedRemoteTrigger/pipeline/RemoteBuildPipelineStep/help-preventRemoteBuildQueue.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | Prevent Remote Build Queue 4 |
5 | Wait to trigger remote builds until no other builds are running. 6 |

7 | mandatory: no
8 | default: false 9 |

10 |
11 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ParameterizedRemoteTrigger/pipeline/RemoteBuildPipelineStep/help-remoteJenkinsName.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | Remote Jenkins Name 4 |
5 | The name of the remote Jenkins as configured in the Jenkins global configuration (Manage Jenkins > Configure System > Parameterized Remote Trigger Configuration > Remote Hosts). 6 |

7 | mandatory: yes 8 |

9 |
10 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ParameterizedRemoteTrigger/pipeline/RemoteBuildPipelineStep/help-remoteJenkinsUrl.html: -------------------------------------------------------------------------------- 1 |
2 | It is possible to override the Remote Jenkins URL for each Pipeline separately. 3 |

4 | mandatory: no 5 |

6 |
7 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ParameterizedRemoteTrigger/pipeline/RemoteBuildPipelineStep/help-shouldNotFailBuild.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | Do Not Fail If Remote Fails 4 |
5 | If this option is enabled, the build will not fail even if the remote build fails. 6 |

7 | mandatory: no
8 | default: false 9 |

10 |
11 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ParameterizedRemoteTrigger/pipeline/RemoteBuildPipelineStep/help-token.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | Remote Job Token 4 |
5 | Security token which is defined on the job of the remote Jenkins host. 6 |
7 | If no job token is needed to trigger this job, then just leave it blank 8 |

9 | mandatory: no
10 | default: "" 11 |

12 |
-------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ParameterizedRemoteTrigger/pipeline/RemoteBuildPipelineStep/help-trustAllCertificates.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | Trust all certificates 4 |
5 | 6 |

7 | It is possible to override/rewrite the 'Trust all certificate'-setting for each Job separately. 8 | Setting this checkbox to 'true' will result in accepting all certificates for the given Job. 9 |

10 | 11 |
12 | If your remote Jenkins host has a 13 | 14 | self-signed certificate 15 | 16 | or its certificate is not trusted, you may want to enable this option. 17 | It will accept untrusted certificates for the given host. 18 |
19 | 20 |

This is unsafe and should only be used for testing or if you trust the host.

21 |
-------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ParameterizedRemoteTrigger/pipeline/RemoteBuildPipelineStep/help-useCrumbCache.html: -------------------------------------------------------------------------------- 1 |
2 | Set this field to enable cache of the crumb of remote server.
3 | It'll be more efficient for the local job execution & more stable for remote server when massive concurrent jobs are triggered.
4 | This cache will be cleared every 10 minutes. 5 |
6 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ParameterizedRemoteTrigger/pipeline/RemoteBuildPipelineStep/help-useJobInfoCache.html: -------------------------------------------------------------------------------- 1 |
2 | Set this field to enable cache of the job info of remote server.
3 | It'll be more efficient for the local job execution & more stable for remote server when massive concurrent jobs are triggered.
4 | This cache will be cleared every 10 minutes. 5 |
6 | -------------------------------------------------------------------------------- /src/main/resources/org/jenkinsci/plugins/ParameterizedRemoteTrigger/pipeline/RemoteBuildPipelineStep/help.html: -------------------------------------------------------------------------------- 1 |
2 | The `triggerRemoteJob` pipeline step triggers a job on a remote Jenkins.
3 | The full documentation is available in GitHub.
4 |
5 | Example: 6 |
 7 | //Trigger remote job
 8 | def handle = triggerRemoteJob(remoteJenkinsName: 'remoteJenkins', job: 'RemoteJob')
 9 | 
10 | //Get information from the handle
11 | def status = handle.getBuildStatus()
12 | def buildUrl = handle.getBuildUrl()
13 | echo buildUrl.toString() + " finished with " + status.toString()
14 | 
15 | //Download and parse the archived "build-results.json" (if generated and archived by remote build)
16 | def results = handle.readJsonFileFromBuildArchive('build-results.json')
17 | echo results.urlToTestResults //only example
18 | 
19 | 
20 | //List other available methods
21 | echo handle.help()
22 | 
23 | 24 |
25 | -------------------------------------------------------------------------------- /src/test/java/org/jenkinsci/plugins/ParameterizedRemoteTrigger/OpenTelemeterTest.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.ParameterizedRemoteTrigger; 2 | 3 | import com.google.common.collect.ImmutableList; 4 | import com.google.common.collect.ImmutableMap; 5 | import hudson.model.FreeStyleProject; 6 | import hudson.security.AuthorizationStrategy; 7 | import hudson.security.SecurityRealm; 8 | import io.jenkins.plugins.opentelemetry.OpenTelemetryConfiguration; 9 | import io.jenkins.plugins.opentelemetry.OpenTelemetrySdkProvider; 10 | import edu.umd.cs.findbugs.annotations.NonNull; 11 | import org.junit.Rule; 12 | import org.junit.Test; 13 | import org.jvnet.hudson.test.JenkinsRule; 14 | import org.mockserver.client.MockServerClient; 15 | import org.mockserver.junit.MockServerRule; 16 | import org.mockserver.mock.Expectation; 17 | 18 | import java.io.IOException; 19 | import java.util.Arrays; 20 | import java.util.Map; 21 | import java.util.Optional; 22 | import java.util.stream.Stream; 23 | 24 | import static org.mockserver.model.HttpRequest.request; 25 | import static org.mockserver.model.HttpResponse.response; 26 | import static org.mockserver.model.JsonBody.json; 27 | import static org.mockserver.model.Parameter.param; 28 | 29 | 30 | public class OpenTelemeterTest { 31 | @Rule 32 | public JenkinsRule jenkinsRule = new JenkinsRule(); 33 | 34 | 35 | @Rule 36 | public MockServerRule mockServerRule = new MockServerRule(this); 37 | 38 | private MockServerClient mockServerClient; 39 | 40 | private void disableAuth() { 41 | jenkinsRule.jenkins.setAuthorizationStrategy(AuthorizationStrategy.Unsecured.UNSECURED); 42 | jenkinsRule.jenkins.setSecurityRealm(SecurityRealm.NO_AUTHENTICATION); 43 | jenkinsRule.jenkins.setCrumbIssuer(null); 44 | } 45 | 46 | @Test 47 | public void testRemoteBuild() throws Exception { 48 | disableAuth(); 49 | initOpenTelemetry(); 50 | String[] allExpectation = setupRemoteJenkinsMock(); 51 | FreeStyleProject project = createProjectTriggerFrom(); 52 | //Trigger build 53 | jenkinsRule.waitUntilNoActivity(); 54 | jenkinsRule.buildAndAssertSuccess(project); 55 | mockServerClient.verify(allExpectation); 56 | } 57 | 58 | @NonNull 59 | private FreeStyleProject createProjectTriggerFrom() throws IOException { 60 | FreeStyleProject project = jenkinsRule.createFreeStyleProject(); 61 | RemoteBuildConfiguration configuration = new RemoteBuildConfiguration(); 62 | configuration.setJob(createJobUrl()); 63 | configuration.setPreventRemoteBuildQueue(false); 64 | configuration.setBlockBuildUntilComplete(true); 65 | configuration.setPollInterval(1); 66 | configuration.setHttpGetReadTimeout(1000); 67 | configuration.setHttpPostReadTimeout(1000); 68 | configuration.setUseCrumbCache(false); 69 | configuration.setUseJobInfoCache(false); 70 | configuration.setEnhancedLogging(true); 71 | configuration.setTrustAllCertificates(true); 72 | project.getBuildersList().add(configuration); 73 | return project; 74 | } 75 | 76 | @NonNull 77 | private String[] setupRemoteJenkinsMock() { 78 | Expectation[] metaExp = mockServerClient.when( 79 | request() 80 | .withMethod("GET") 81 | .withPath("/job/remote1/api/json") 82 | .withQueryStringParameters( 83 | param("tree", "actions[parameterDefinitions],property[parameterDefinitions],name,fullName,displayName,fullDisplayName,url") 84 | ) 85 | ).respond( 86 | response() 87 | .withBody(json(ImmutableMap.of( 88 | "_class", "org.jenkinsci.plugins.workflow.job.WorkflowJob", 89 | "actions", ImmutableList.of(ImmutableMap.of("_class", "someactionclass")), 90 | "displayName", "remote1", 91 | "fullDisplayName", "remote1", 92 | "fullName", "remote1", 93 | "name", "remote1", 94 | "url", createJobUrl(), 95 | "property", ImmutableList.of(ImmutableMap.of("_class", "somepropertyclass")) 96 | ))) 97 | ); 98 | String jobQueue = "http://localhost:" + mockServerClient.getPort() + "/queue/item/311/"; 99 | Expectation[] jobBuildExp = mockServerClient.when( 100 | request() 101 | .withMethod("POST") 102 | .withPath("/job/remote1/build") 103 | .withQueryStringParameters( 104 | param("delay", "0") 105 | ) 106 | .withHeader("traceparent", "00-[0-9A-F]{32}-[0-9A-F]{16}-01") //https://www.w3.org/TR/trace-context/#traceparent-header-field-values 107 | 108 | ).respond( 109 | response() 110 | .withHeader("location", jobQueue) 111 | ); 112 | 113 | Map mockQueue = ImmutableMap.of( 114 | "_class", "hudson.model.Queue$LeftItem", 115 | "blocked", false, 116 | "buildable", false, 117 | "id", 311, 118 | "executable", ImmutableMap.of( 119 | "_class", "org.jenkinsci.plugins.workflow.job.WorkflowRun", 120 | "number", 34, 121 | "url", "https://jenkins-himalia.aws-devops.itsma-ng.net/job/test1/34/" 122 | ) 123 | 124 | ); 125 | Expectation[] queueExp = mockServerClient.when( 126 | request() 127 | .withMethod("GET") 128 | .withPath("/queue/item/311/api/json/") 129 | 130 | 131 | ).respond( 132 | response() 133 | .withBody(json(mockQueue)) 134 | ); 135 | 136 | 137 | Expectation[] jobResultExp = mockServerClient.when( 138 | request() 139 | .withMethod("GET") 140 | .withPath("/job/test1/34/api/json/") 141 | .withQueryStringParameter("tree", "result,building") 142 | 143 | 144 | ).respond( 145 | response() 146 | .withBody(json(ImmutableMap.of("_class", "org.jenkinsci.plugins.workflow.job.WorkflowRun", 147 | "building", false, 148 | "result", "SUCCESS"))) 149 | ); 150 | 151 | 152 | Expectation[] progressiveTextExp = mockServerClient.when( 153 | request() 154 | .withMethod("GET") 155 | .withPath("/job/test1/34/logText/progressiveText") 156 | 157 | 158 | ).respond( 159 | response() 160 | .withBody("job output") 161 | ); 162 | String[] allExp = Stream.of(metaExp, jobBuildExp, queueExp, jobResultExp, progressiveTextExp) 163 | .flatMap(Arrays::stream) 164 | .map(Expectation::getId) 165 | .toArray(String[]::new); 166 | return allExp; 167 | } 168 | 169 | @NonNull 170 | private String createJobUrl() { 171 | return "http://localhost:" + mockServerClient.getPort() + "/job/remote1"; 172 | } 173 | 174 | private void initOpenTelemetry() { 175 | OpenTelemetrySdkProvider openTelemetrySdkProviders = jenkinsRule.getInstance().getExtensionList(OpenTelemetrySdkProvider.class).get(0); 176 | String mockOtelUrl = "http://localhost:" + mockServerClient.getPort() + "/otel/"; 177 | OpenTelemetryConfiguration config = new OpenTelemetryConfiguration( 178 | Optional.of(mockOtelUrl), 179 | Optional.empty(), 180 | Optional.empty(), 181 | Optional.of(1000), 182 | Optional.of(1000), 183 | Optional.of("jenkins"), 184 | Optional.of("jenkins"), 185 | Optional.empty(), 186 | ImmutableMap.of("otel.exporter.otlp.protocol", "http/protobuf") 187 | ); 188 | 189 | openTelemetrySdkProviders.initialize(config); 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /src/test/java/org/jenkinsci/plugins/ParameterizedRemoteTrigger/RemoteJenkinsServerTest.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.ParameterizedRemoteTrigger; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertFalse; 5 | import static org.junit.Assert.assertNotEquals; 6 | import static org.junit.Assert.assertNotNull; 7 | import static org.junit.Assert.assertTrue; 8 | 9 | import org.jenkinsci.plugins.ParameterizedRemoteTrigger.auth2.CredentialsAuth; 10 | import org.jenkinsci.plugins.ParameterizedRemoteTrigger.auth2.TokenAuth; 11 | import org.junit.Rule; 12 | import org.junit.Test; 13 | import org.jvnet.hudson.test.JenkinsRule; 14 | 15 | import hudson.util.Secret; 16 | 17 | 18 | public class RemoteJenkinsServerTest { 19 | 20 | private final static String TOKEN = "myToken"; 21 | private final static String USER = "myUser"; 22 | private final static String ADDRESS = "http://www.example.org:8443"; 23 | private final static String DISPLAY_NAME = "My example server."; 24 | private final static boolean HAS_BUILD_TOKEN_ROOT_SUPPORT = true; 25 | 26 | @Rule 27 | public JenkinsRule jenkinsRule = new JenkinsRule(); 28 | 29 | @Test 30 | public void testCloneBehaviour() throws Exception { 31 | TokenAuth auth = new TokenAuth(); 32 | auth.setApiToken(Secret.fromString(TOKEN)); 33 | auth.setUserName(USER); 34 | 35 | RemoteJenkinsServer server = new RemoteJenkinsServer(); 36 | server.setAddress(ADDRESS); 37 | server.setDisplayName(DISPLAY_NAME); 38 | server.setAuth2(auth); 39 | server.setHasBuildTokenRootSupport(HAS_BUILD_TOKEN_ROOT_SUPPORT); 40 | 41 | RemoteJenkinsServer clone = server.clone(); 42 | 43 | //Test if still equal after cloning 44 | verifyEqualsHashCode(server, clone); 45 | assertEquals("address", ADDRESS, clone.getAddress()); 46 | assertEquals("address", server.getAddress(), clone.getAddress()); 47 | assertEquals("auth2", server.getAuth2(), clone.getAuth2()); 48 | assertEquals("displayName", DISPLAY_NAME, clone.getDisplayName()); 49 | assertEquals("displayName", server.getDisplayName(), clone.getDisplayName()); 50 | assertEquals("hasBuildTokenRootSupport", HAS_BUILD_TOKEN_ROOT_SUPPORT, clone.getHasBuildTokenRootSupport()); 51 | assertEquals("hasBuildTokenRootSupport", server.getHasBuildTokenRootSupport(), clone.getHasBuildTokenRootSupport()); 52 | 53 | //Test if original object affected by clone modifications 54 | clone.setAddress("http://www.changed.org:8443"); 55 | clone.setDisplayName("Changed"); 56 | clone.setHasBuildTokenRootSupport(false); 57 | verifyEqualsHashCode(server, clone, false); 58 | assertEquals("address", ADDRESS, server.getAddress()); 59 | assertEquals("displayName", DISPLAY_NAME, server.getDisplayName()); 60 | assertEquals("hasBuildTokenRootSupport", HAS_BUILD_TOKEN_ROOT_SUPPORT, server.getHasBuildTokenRootSupport()); 61 | 62 | //Test if clone is deep-copy or if server fields can be modified 63 | TokenAuth cloneAuth = (TokenAuth)clone.getAuth2(); 64 | assertNotNull(cloneAuth); 65 | cloneAuth.setApiToken(Secret.fromString("changed")); 66 | cloneAuth.setUserName("changed"); 67 | TokenAuth serverAuth = (TokenAuth)server.getAuth2(); 68 | assertNotNull(serverAuth); 69 | assertEquals("auth.apiToken", TOKEN, serverAuth.getApiToken().getPlainText()); 70 | assertEquals("auth.userName", USER, serverAuth.getUserName()); 71 | 72 | //Test if clone.setAuth() affects original object 73 | CredentialsAuth credAuth = new CredentialsAuth(); 74 | clone.setAuth2(credAuth); 75 | assertEquals("auth", auth, server.getAuth2()); 76 | } 77 | 78 | private void verifyEqualsHashCode(RemoteJenkinsServer server, RemoteJenkinsServer clone) throws CloneNotSupportedException { 79 | verifyEqualsHashCode(server, clone, true); 80 | } 81 | 82 | private void verifyEqualsHashCode(RemoteJenkinsServer server, RemoteJenkinsServer clone, boolean expectToBeSame) throws CloneNotSupportedException { 83 | assertNotEquals("Still same object after clone", System.identityHashCode(server), System.identityHashCode(clone)); 84 | if(expectToBeSame) { 85 | assertTrue("clone not equals() server", clone.equals(server)); 86 | assertEquals("clone has different hashCode() than server", server.hashCode(), clone.hashCode()); 87 | } else { 88 | assertFalse("clone still equals() server", clone.equals(server)); 89 | assertNotEquals("clone still has same hashCode() than server", server.hashCode(), clone.hashCode()); 90 | } 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /src/test/java/org/jenkinsci/plugins/ParameterizedRemoteTrigger/TestConst.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.ParameterizedRemoteTrigger; 2 | 3 | public class TestConst { 4 | public static final String garbled5KString1 = "ivxuKazauCeEHuXxTkp5hbzFUqFcvaCNmimtib96VkuhX5DdQpP57TMJ9iCNKeRQzjGzjmCP6Hwd8H5zLhjJ2BWYAm5Kd2eHGvKqLUP" 5 | + "MqRxuJYUHenA3ngXt42AzheFy8xr8xCVmwpVGU7M4AE8AZ3ran3WfQTnBYbixLHfMYYjmM6de2GqjNvQXdUFht4LpSkXY2DFLu4nUx6ENkarfpeSu2MxruUJEqpZ88VSHhVQ8Pid" 6 | + "tyKgfU3LWfHArurLu2MAg9nm8WMaxBmHjrPMwkqwHqvNSwEX6Ah9vw9MKQVxTKarZtb5TYbCrkL7wPdWZapSnriWJudtfYZMaVQqdcKhFiz7SxWxza7L9MgHDH8wFhVwpALTnGVD" 7 | + "am7MNU7Vb5vcRv3MckwWjuyGMBQqDtCTxpfBKLX77UghhRAPAfuFAFM7kSK6m74wGzEDgv5ekUYNvngt6v2eSweHxAhKcUqUA2KRZKfeLFrEkqzfA8CZCwW3CKfTUyKAqkENNSXkQ" 8 | + "QHpRAMqEnPxgd7p2BmB85FqvVFmK9wZztXJt6Nni7YYq9FGqaxfpPVuwxfQ8Jb5bvdU8WfGnVChPDdM5Hy5CzZbFw57cufXT7c2EMpHyXYZz86FxcM2St8PjPwafFN4YWXCAb5Lm" 9 | + "95XSzjh3ZSR6tg4tQ94CMBTqVnchurvE25fZGq5v7HRFV9kdKXbzgchkM3HinhuRQJBAZKwvgdd7mdB6DEMCjnKxZXQrKkiB4de58fu7dwizVgQgGe2QXFyrVdb5BWiphLthknqS6" 10 | + "hzx3CCwxPZD7rwf7hYaS4pmkrP52LuGrAaTQg6HxEJhdkdaWhcN2U4yKie5Jn23PUqDYt48YYFd3xvD238wqwywgeG63k8r3Nh6hC7H6YQfqTW2EcH8Pap2E5c7M9YX7D4LEfWQwx" 11 | + "jXiHCUygEDpzPu7Z2DFzvgZxDKJCcuAXPXEuhTPU3xLXMPXQJaxMfXF9Gfe72rp9gqy4Q7rqpVHTDURM3B2rTuHPaVjMPMvi3BK55VzXcKi2t8mVfcg58Ugbffw2NZVXyDjataQt" 12 | + "DupjKxqVAvQuiEkKRaEU3eHrhP5erYUhX3BLhHT3LQU2u7gdkb6NbaGDzKA3tjx2DY8xNL3QcBbR7ra6Rg76pEN5xniuAkEgxACTWNqNVNuufGrZfDDeJ7NcEUmNZhBFidNL3f34" 13 | + "VEN7kKRUBH3JefxV4Z3fMLkpJkPD45Xx9hkhkckufxjrJSeSyTYeVYADhRc4qPrEceCXAjgpZLh4rvh8bJc3tyRDT3nihvzZHAaThhtzPgjnPKbXyLynNbdmfrUCWypz28F7SN3kx" 14 | + "XAPUS8baWL4VVYQQ2iyyPQ9QkAycubeUcAv2xwej5iuKcwaGcHPKjAatMErAA3wn7E3zuvQUKGi2yi7mqNXnTgE3kq9d9jV6vZDi7tRaBhqeMSiS8gNTfcHnrSyk6UJdMCAgYnX9Y" 15 | + "2y9dHdp7vwci7rV7anmRXYjqq3eeyPhLmFF6V4EFtFixpU7LdcJzmjzxuxCp77NUXii6hf9tnMyYMDHrWvrvaTctxBk67TAzZVXYPkxKYNXrfU86DM4rGTycNRvLJaEQUBQZhynLj" 16 | + "Qyyh9TvCcbgrLiFRjRXhfKwKR2B2PaHL7fZ3TgyVAbKmMa3r4RDeeMtgBa6fVv54RVP4RiD7rmFqUFpqy2VWkGABCSHKiWWcBmikUzCjYz9KcUkPkjfBVqcZe4uk4yBySb8ubhPVX" 17 | + "M76H7d2VBXXyJiAfGfC5q6T663zkDZYRrGHfDeHcSLU7uYqJ3RQwpqZA6axgftSKJnRCGSv3iMpWnayBAreXrw6bvRAgA5J4vvFbdi7E8TBriStZ7wir9KLZ8uGhbQ5DUEAN4YuwM" 18 | + "iEvx7bYwA7vGzAnGWJHwzWPiTtgquASCHbcXPeW6hYhVQMSf6SUkATKFRLQVLbkQzQnxvtAYNEKr3q62wWvWtNgtW8bezVPTHrMVVCe3e6XkPaTdjCr7cxW7QVcYVBiuF9H6TmCmw" 19 | + "WemkJBhC2BYyBXSGDCWkZS6AvNjjNaJHmD5H3cpbec8j8yRiDY25gSH2HX7nkuA3pcGVxZU455WvkcVZNd5caSxSzpRtB2ADjKx66h2p49biG2nKkBhfhzPR6J6L4LRD5udL8mi4" 20 | + "XVkW5JBQMvZi8Cwtt8DtTSMPyaq3uAHBY94GQQenVJyUyRq6Rh8g3THXS4EeAm4K9MEaJdYHeMnnGY8hXBRDeANFFPFrWTKbB73hDVAkRzEyW52McFFgK4KByeDqxZGyqQ4Ch6uLe" 21 | + "LfkY3ZwMnKMcMLMYXPEy9iwRnuyL5xmR9NU8YcnhrvAcJmLmJKqxbXEBSiRmNaDYDnnhAiYgQETAfw7kGCrnbrK6GbRuqZAzA8CTdxcqHxucxZXyRdDqhBTCKcTcXnBu96X43kqmS" 22 | + "gxn5vHRmQ4Wfjki4RYLVvN7UZGCCDVJGZZrWKFH7bDgeih5ptMMpjyYBmc25VDXA8X4UUQrfauiX4XpP2LnKTb3jHJh6JT7JmzD2HipSWNiPeP2BpYnhAgyuQU4qWjtRWEfLdGKkj" 23 | + "QBazv5X7FPKtPwm47Kz579cyFHbm4uhttjNrfhQDy9vWDXw6umhE48rhViVhmutqraFL9ZPpfF6bzinHMYyvj9BqyunVESYdi4c2Hf5jFuycEyapUjTY5yAgRWPczU6gh9TnRJJZZ" 24 | + "EmwZ8m9ubT26CcBC7wEPjV7Wd5dUZuE9LQ9K8ziFrcpqEwbxQPACZq89RpJ72KDjayUUDSqLUVDuDRjTer3futTYcmqAaXkhQyVkcA7t7QWQie7qW2m8JSpkriAw8mN7nUbEJ76G5" 25 | + "FFfkLFtH99ajmZ872AZP9RAxd2Tzx7YYDSXt9hy6tYgVt2u2zVTUe8T8PSFnEdgrCDhb35U7drJY23Yn63C7iH9bSEPiC4VfEjDv85akuYmYGiPh3JHAFyG3jryXPRuyNDLMw3Xin" 26 | + "VbWBcHyD67CJnBLD4Xm6CG2tMcNrznvQTKmvw9zJtamVmXzbcC7xtQ8zn3Q6KFWVYaJ89mCw3wv9tMdKHkPT9wCQb8NkpuaAU2g4fMqtpD227QyYh6B829pRcwtRQJS3rF4bxhAau" 27 | + "uSEptiUD435rzhMnt4XZmzVN4Z54cKmDZKWJ4pix9mVnypur7Bv2QW7ckRGiZ2BbSMCfTi3UTiXnyP7wjjmX8JbukyNVaGKiw279MYjpCbg72TZrh7hAakvvrbEj9kGX759Sc7HNY" 28 | + "ZgajYpxPzYQw846puC9BATLHPFAQpjQkjeVk2dx6bSKrXK7kN39QDLhjSeTwGQaUuTQYU9cbTnU67RPn2HceNuK6wJBiwKdZb7PAiqRHk4zTTUuWZLf8Tcdqud6RfVZJvZmvSnqa" 29 | + "Vq9eGdD9G37tUcpHLY4FAT2Cu6NC7YMqLQcJwW5Z32n2nw5pzSytaKkb68RSQvubDbrTJ7yPj4iF4nLrKtTATMx7w2qyerbTeKvnv6dxP8VWBwdQxygKggSS8WUL2cZgHNrdH2rXG" 30 | + "fXH4gU3p4FXb5w3zdBMkDVSfiMWgUSA7A9VuU4crtJmRTLxS2eEhhH2gXaLgTJqMeqR4YGyHgxwfrYGDzKNKeuu37h829QyjBmWceyiEZVGF6f7mLKS78xEy5CQPqAQqBZk4vHeZN" 31 | + "YJTh6et7tJS4SFAdEQvSmg3mCmNYkKTb9iuWhqjFAmi5Uu7NhYnzqML3UY9hAD8B6zMuYvDWdNWrw56mePGF97b258hKeiZMRmVxFvP8YcqR8Gmbuic7HPjD4kTYwrzWkKteyCXf" 32 | + "e9KtTPqZKzeBy8rL2Cc6ZFvgW7MMC8QNE2e73qiPNDkvvZDemECbRXed58DpaxFCF4CLjh6ytm27UAB4jSwvjYLYjcdfqTZtVRyN9FCqwKNKrFNHY3H9tdYRXycmLXCnGQKFiRhF" 33 | + "nEZtHXizG2e7AbyPvnej6S9chNKXzv6fU99VfCYxdzbje5GzWSzP7vwDmMNUg9NKTdDPrJA99XeVN8c2R8GavaJBpvUrCUZWCJHVRYBd9x39WgkbkACHiHiMfq39PKxgzwtEftgXg" 34 | + "TrVX3XSiuKBrEXAxMXeAtyiSjDtC5uCMS8cYDbHhPrEdXk8gjywX5fPg293MayrQTTJhXC6d3pjnS8t8SvcHD9CvKPJjF6W4W7M8Ahd2nEJiYHRyKudC6inM6xHtpKfL47xAWKdT" 35 | + "WnTbECJyqTYRxSfEJdTtHtJ3RMt5mAUBGcNnEDfn7PUwT3hnNMHhkfh8AcU564EQMyNJcUnv5Bwg74tHaraQxyRKAx7ZG5w8RF3tgAHvCV7t3ETz2krRwEn4UH7wzY2yfbX6wYpQ" 36 | + "f4Tqbb8nv4QdcdhZVZNwbPQbaf6qgnc6LZ3GFpQTV99CMuGj8mg3zUkwfRDAK4ffiUTktYv3ZLYivYSn5BQpdDMe45a323hY9xny5cEhq2fBjPSQ8k3Y3utBgkcJhR2TfWzV7jg5" 37 | + "Ti3wbwHxeXfBUj5vXfDxUgZdwtKg53Tp482iguJ7dzYHbhk66wzYDmERunpp84pwjZinVVLRKehSDdPpQqqqSUz8ApnGyBYykDuT8cKGhgtW9mzffPAbH8Nnhw7vYnZHhMbLDSXX" 38 | + "VSbU6HU25Vy7xKf2wHTdkT96avqbqV5MPMrz35rY5eRHdPCKyi7NaLj3wrPqxN7HDYcRbTDLt9EAqUC7rrfp3BFWZwHMMcxaBQcSPyEGuT2hVCS76UrFUPCCVTxp77EdvUSLmhxh" 39 | + "KhpbhanYK9bBUv6eAGTxyw8SSnZfN5WPCTL99MUxkYmTn3yJvtPyH9puEX8YnNnp6pQbzEFce8TEH5vLMjZv9Ljn6MnJWmv7C6i2JZLVEyHztfCYvMQZj9X9yvp9fEZQvQdTec7m" 40 | + "5KeAxd4NXuUtyj5FuyGYANYWjFhVRD3Z2mhBBfcwcwu3DCzYWZmtWMeSSRbmpYhhZ7RHZvgahDn34Q6vZ7yXZwEhMHxJcEPDWS5fQ4p29HjiSFc9iKVuD"; 41 | public static final String garbled5KString2 = "h5RV8eEnPk2jyfShzrMUJRzE2Y5XvzAEygg6ZVRT7Upn98Hkx9wqjUNg2xmpTSmPREzXtPevMDMGMXeWR7MC5cn6xvQXyExRPMVjtJ" 42 | + "HrhkQKdt5AEKc5GKVFwfiF4BXf8dD6C7xagcn34GuB4PvciLXJ7ERGPygaXUMVkqhjh9d2vTCxtumbKLeXYbFDzAmyRRZJyPkgnqyzkVTxg937Nz4QtYUNKMVLLvgCpGYX6eC2m" 43 | + "uamtJFPWVd56L62n63YXpyntJQkHcWxwk2CLtH3mKVMhprpRtTE9v59DPifKkYfjiyM8qNMxfZcSk8HdbmhLdPCR5DyLqxf78gtChTmHHwEXb8EwT5UhrtPpF54pVpUE3Jj5r9vc" 44 | + "ChxxZDAfk6Kwv7e8YeUZSHk3X5D6itVSE6UMQZj9L6nhytTynBrftVhrAJpuvBDpqh49n8LGhG283dBtawNm2GMjgEjuwndXKyhAqBvqkGgGGdyuKbCtM75VaFUTSfy9qx8MXHFFJ" 45 | + "P5mYR2rQgEyCHLyHXMvz84HWcAqPWuHUayd88wpzwgeiMxj5J5BrXGgPXnxbmyMFgjynBhYg7YyfLCk3hvP2wZcqMqHpvRktYTiBJ7AKvfE4w5NKY3BePtKaZuCyLHp8vkX9AXH" 46 | + "7cADHGUixVgCfYjYLTQHDCQiarwBQqMVTBkvUCRb6ZvcLiRBf5DKbybh7my3Jpeui4UD96iqHJUwVhytecAwe4iXjSfMe3q9x3qKcDpfQuGghx5pjfJ94BBFvTGRDRqvHKzLpBRC" 47 | + "vdqnRPPC4vyNjbV6YqJAGHceTrXpCkuZcPhhxFBvdcWPZiFcW56HhwA8BvHnxRd5iBLV3GcaxWCctRaTZW2cBGvuyBAfDHYZVv7hPg8tW69YWTyCQvCAVDJSr7iiBW7gJUrjSw5N" 48 | + "hBRuHxPGDib3TLqCMC9fcZX7XctptkgjBfhnbpPJ8wEmE4CVwXYpqF97YKMU2SyNFAERWK6QRfPhXwuK9UXc8jXe2QEr2ZF2eamDrvzScxy4z5Vaafhi4kiL7xXEkYJcKFrrgzx9" 49 | + "qyHrkumAdegFpVZQn6yUXpnndKQSYyXfhWQhnM2QayxHFGWp78SJ8ZqzTtNbwyDUeLqUUhmfxub9NeUny8eSZiY6tdyJkqawtSQJEXz9krpqdGGxJPpfpZiD3RSikAavWe4mHa8m" 50 | + "yJCuSHzxtaL48cvyKPiJyFqYGy7dj43tTQXfiYuxSTTNkxr9wwurmCFBXrMDNM3PXKt7xYDRkkCKHLhyYTmVmBKq2EcSdZbxCfhnGvJe3qycSBHMmP9Qf2672F2Ep5Ccku9cv3NV" 51 | + "jC8pYmCdrx3JgiMkCghSrr3tpw4deZGPiGjiR2D9thu74rF4rVvc9HxfeREhBKWprKey4SAgdcJqqzZuEgKKAVkrd57VNy8UMJCCQg7uKrRxXPdfCMtyPT9RFnjdbiX6CUCekNh2" 52 | + "AFGNqEhpQZqVjSNpq5ecwHhmctwpM98tuk29Tz3bwwPJa3SzCinm6PLeCcXUWBuhLkA8hZnxTUEayLyTnNc9Z2ngjhpaD5ZpH2hacaMLZrVHKu5QUc2WVnNxDmDRdGhbGUfpRWBy" 53 | + "k2ZmNz26q6pU3vBQQAeM8jrHcY78h892naukUNwVb5gaxkZnd8YrimfNj8V48VyTun62BZ8EEMg6qqhnhfheaa44D4RyYWCgEam244N9PmdPifDDDUtiEzYqtfPfeRLjUmktA99f" 54 | + "YZju3TNycgF4C45zEyaqnrUXyukEABewyGXjCXt5FR5dBAGi6ezixvFzAbQHDVhhfJ7ngR8za8CBBqRRHcQNz8nhCq5iewcyqpbRMt6xtS5cgwFvBZ8prNbKkVR5pEpKCjqzrb5n" 55 | + "RvaP6GptKu7AeFKeqGTNxuHvxVhAvxCRUDTxFw4bk7w7tCQEQ3f7Zk6mQgr6cefewCxHvHMukywrn6EBMakgY5DWDB6BXn7YGQ7nQ694MDUkG63zWWgWQg6KmHzKafJabTLLwGwm" 56 | + "F9Qj9HkRB6QSY7Ty2NEwKePybpbK73VybNNSyrkpLYhuKqhaW4PyR2HvQZLHz7bMeKcfreSTDLSQNcGVFAitFt9rFGm2YQMdnabxBE3TNmDS3utNGFEexjYLA3XbhgKw6zDQCfX" 57 | + "ux6YEepFYmatUXatEKnNnnRDCwJGLRNHPZVmrYq64bTUR3KMy9jpaRjvNv68Gzd54KaqLaYdtafHgX7g5WrhbGZVqg7hvkGeyQXdN8wEjAeMgMDnyZUU5fJgPCVAQWLAZGrzyaK2" 58 | + "Uu8dpqttfyd8PJccXD9Y9YkWynuNwMdSJKKKPuCTrddDGgtq9FzREYCRGC7Y3Zj3EF96BxT7kFyWStLbdc2VvR299QwAvwgcNp8KiL9E6vNDWKz5F9dik5ze4ecUd6V7uqTLDEHk" 59 | + "4VQdQSVcQg8Gn2FgmwQaba5xRQvkymK9RM9tnkQNPeLQRR33pf3yHcCFXKTKNAAtDwZBwrXfXwSt3bb72DTfRXTP8kK9KWNQhH3533DQSvM3xAmJFEG6BTf89QuYJgm7yhbzRNkm" 60 | + "ThEcciiGxkceRRdAHBpxiZ9CYvraxjtWLF9hNWX9bJLDrnqE6VYuATDrgE3F2Rg3ZFpVBZpy2tC27paVYBfxaYpyxNVXjnP3bFUHNxfh9TjprtMXu6Xr685tgqn6bDqMXGYcFCXG" 61 | + "jUdJ4qDUm6br74hAgUycjXgfeyVjvdWy3mG9zZmNeWEfU6eaACLcQX3fn3uTk2zaNVWSWPAzFmuGT7bTAFShdNGcMd22viUCG2pxMTPVMqJ8tjDjmwTd2WUXG4c7v8FVC9egzRj8" 62 | + "Q55nABG6FVVRLwYQkYVAk9FkDGrYKRcQCJLrjDnwAWLKuqrQxBXPC4DU6ztBJbMxNZkPU5zZ75SqE2AQLbZF9i2QrM2abg4jCkjnefTBTpzNgWdVkAA4E5egKXdJZyXQuZrAQCp" 63 | + "CMpUeiM8FhTEZVKhT7ytEAXy9M9DnMUh8JPfPtWDSx9KFYm7pbrwLvPKZcTKFd68urnKg7SgbM36Tb2NjWpegzSd6gqYtJ698WXQBArnpgcuNjG6kNa3pNe8XNp2C7VmFLANP7E" 64 | + "W5b95NGTMB9tzrijUu9rR7Tzq2Xi7QXNniPvgFiWC85SBgJ4KN3JXrQrGxCW27v29phwR44m4vzfxxwfB8adFTgC6AU9KRUF8HFVCMxnkjRYj3DZbS2G85Z2mixWgDN4hR2e9pb" 65 | + "ixDCLxUuF3aCCVJZ9rdaFSK3vk2gwrVkEwhBD3XWxwST4PfqYePAwFgrgvjBN33BELT6FSRKruYMzAkkGryt9r53CPmRdU5fKMzXxtPTGrhAyxJmitMtabJjj8f9WQnnMtcqWHTM" 66 | + "DT2jLbFWUaktyyeTwXnR37g22DeRd63unUpP4kE3bNwv9ZPvSg4ES3ehgNf5aj6veTctaGCBmLMFZhQuVzp5vXt2QgNa4iSR2JdDpBYcgpub7GpyeGp2RkDZh3S3Rjfcv8k5GSx" 67 | + "2N6BHxUhMUvAbevuMMVwh8enLBz6SLq6fFztfGfwCeCuS9fD3KGiFwiwQatB3ZE8ScVhXf6AjCuGYWPMZPyK56njcqL4NFECbiEN8jeugfYjSh5TypDkhDrHifaiCVuDSitKdNzU" 68 | + "B5968FdGELVSNQvEDCuNqT9RLLTUGiJ2KG4i5vrF9ccXdPzaRNUQffaYxJwFKb9F8P4SmhPdqcuULjeetRm8BfwmLhpSYihuzQKRMUkZbBKgCY3eeEfU7Vif44xQA7e6mr6Qrbt6" 69 | + "WrzytRuWQKcPyj29DUk8SiAcMSkYYLciHqWw6B89b9PKv48GY7Lq6NchTGULFu6ErWc2MYxWmcwBacuGUj9tSXERcf8HwAuPW85RknaDdpM3FBjMTvxnN9LnWgZkuL4mFU66tUSn" 70 | + "VJ4nUzfbYfSRwehQ9SJhammDiANDntCfXdQGdKEhcqbLWEdChmgaXqX3DwHwADBEYvugN8ngJ7USRz5hLmx6uDc87atDL55JLD3qjpw8hjv4CRztuhVfeqShCwV4CA84i3cY5LuE" 71 | + "wBVXrDLpK5494Q8Gkcc6MJiTLMyYUZr42p6D5jBRhEUdBMBeUjgYBN6K6A69GV5mVpySj6JamiECA2yVz8ptpbneZqpRyRmbqieKMrikwJCbBwzz8EmDLKfwpT9DgdwPEkUpXDn4" 72 | + "Nuc5iKwXxnFf4BSTcRZxH5GtwgYvUiDrZqaNBL8SSJe8NU7jB2FGyBRAwHr4VPf2pa4YSDf4pvLvPPpUnm8LJLedAThQTSESfKKbreQYatLVmK7g53mEUijKLceuMawuAawyaLk" 73 | + "xEhPEEhh7vktLReU4kj89XGJFtd6JfQBSJudEjm9WzD6NjHFMacLeeW9QXpvNxEgVqzFU4BNbAaRaTpf83khgzb28eDMCbxEbNHGDkGfkp3kyxRRKpGckeeU7KdqyU85wGp7DG7z" 74 | + "3QDWSE6TvUTdATqn2qKXqSuetUtRwg8gnJTiqYemmBuPt83eKcBxJ2X94TZTqhBnxphKYfcaTpccqR4AbuHHRnhX9MBGEY5F6QFCQyEy4dMyTcFS9NiuTnpM8RJ2CjycuJmgXrQd" 75 | + "TgtdDUCuzhXLWjZUxexhtATUm5NJFeDXg5cD7ddbA9kC6Nxf2PVJDWywbdrqQpxg62jLSgXYcLeaM6YLxSf3RRj7PWKSTWPgrTEDPzcCFK6g8y3ZZG5LqJhkiQKEpCnXwH2vQEBC" 76 | + "zUaCZKJeEvxVx5eQG2vb8eeHTMcbcSY7TfTAHGSN5YWHRMr78ADyac4eYxppghSmkC8fVZViXmKyhpNxyyzESEKNhD8v8e5E7HcyZMRzih6ySXbUSJeLfJiQ7yLNRAtPXi9QcQuT" 77 | + "EzSRcnKMFWFBpEXUweu3gqTczKPY4uLAXwbxqurhm2gzBBPzp7KrwS2byeqJYg9AGSkPZDJd5VdfJBSfH2PUzjVfWaPEUSeaWchFJX387YLUp7LtxfQVVRD2Hc9PWpNB6yVML7fj" 78 | + "EUUSMAgWw"; 79 | 80 | 81 | } 82 | -------------------------------------------------------------------------------- /src/test/java/org/jenkinsci/plugins/ParameterizedRemoteTrigger/auth2/Auth2Test.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.ParameterizedRemoteTrigger.auth2; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertFalse; 5 | import static org.junit.Assert.assertNotEquals; 6 | import static org.junit.Assert.assertTrue; 7 | 8 | import org.junit.Rule; 9 | import org.junit.Test; 10 | import org.jvnet.hudson.test.JenkinsRule; 11 | 12 | import hudson.util.Secret; 13 | 14 | public class Auth2Test { 15 | 16 | @Rule 17 | public JenkinsRule jenkinsRule = new JenkinsRule(); 18 | 19 | @Test 20 | public void testBearerTokenAuthCloneBehaviour() throws CloneNotSupportedException { 21 | BearerTokenAuth original = new BearerTokenAuth(); 22 | original.setToken(Secret.fromString("original")); 23 | BearerTokenAuth clone = (BearerTokenAuth)original.clone(); 24 | verifyEqualsHashCode(original, clone); 25 | 26 | //Test changing clone 27 | clone.setToken(Secret.fromString("changed")); 28 | verifyEqualsHashCode(original, clone, false); 29 | assertEquals("original", original.getToken().getPlainText()); 30 | assertEquals("changed", clone.getToken().getPlainText()); 31 | } 32 | 33 | @Test 34 | public void testCredentialsAuthCloneBehaviour() throws CloneNotSupportedException { 35 | CredentialsAuth original = new CredentialsAuth(); 36 | original.setCredentials("original"); 37 | CredentialsAuth clone = (CredentialsAuth)original.clone(); 38 | verifyEqualsHashCode(original, clone); 39 | 40 | //Test changing clone 41 | clone.setCredentials("changed"); 42 | verifyEqualsHashCode(original, clone, false); 43 | assertEquals("original", original.getCredentials()); 44 | assertEquals("changed", clone.getCredentials()); 45 | } 46 | 47 | @Test 48 | public void testTokenAuthCloneBehaviour() throws CloneNotSupportedException { 49 | TokenAuth original = new TokenAuth(); 50 | original.setApiToken(Secret.fromString("original")); 51 | original.setUserName("original"); 52 | TokenAuth clone = (TokenAuth)original.clone(); 53 | verifyEqualsHashCode(original, clone); 54 | 55 | //Test changing clone 56 | clone.setApiToken(Secret.fromString("changed")); 57 | clone.setUserName("changed"); 58 | verifyEqualsHashCode(original, clone, false); 59 | assertEquals("original", original.getApiToken().getPlainText()); 60 | assertEquals("original", original.getUserName()); 61 | assertEquals("changed", clone.getApiToken().getPlainText()); 62 | assertEquals("changed", clone.getUserName()); 63 | } 64 | 65 | @Test 66 | public void testNullAuthCloneBehaviour() throws CloneNotSupportedException { 67 | NullAuth original = NullAuth.INSTANCE; 68 | NullAuth clone = (NullAuth)original.clone(); 69 | verifyEqualsHashCode(original, clone); 70 | } 71 | 72 | @Test 73 | public void testNullAuthEqualsWithNull() throws CloneNotSupportedException { 74 | NullAuth original = new NullAuth(); 75 | assertFalse(original.equals(null)); 76 | } 77 | 78 | @Test 79 | public void testNoneAuthCloneBehaviour() throws CloneNotSupportedException { 80 | NoneAuth original = NoneAuth.INSTANCE; 81 | NoneAuth clone = (NoneAuth)original.clone(); 82 | verifyEqualsHashCode(original, clone); 83 | } 84 | 85 | @Test 86 | public void testNoneAuthEqualsWithNull() throws CloneNotSupportedException { 87 | NoneAuth original = new NoneAuth(); 88 | assertFalse(original.equals(null)); 89 | } 90 | 91 | private void verifyEqualsHashCode(Auth2 original, Auth2 clone) throws CloneNotSupportedException { 92 | verifyEqualsHashCode(original, clone, true); 93 | } 94 | 95 | private void verifyEqualsHashCode(Auth2 original, Auth2 clone, boolean expectToBeSame) throws CloneNotSupportedException { 96 | assertNotEquals("Still same object after clone", System.identityHashCode(original), System.identityHashCode(clone)); 97 | if(expectToBeSame) { 98 | assertTrue("clone not equals() original", clone.equals(original)); 99 | assertEquals("clone has different hashCode() than original", original.hashCode(), clone.hashCode()); 100 | } else { 101 | assertFalse("clone still equals() original", clone.equals(original)); 102 | assertNotEquals("clone still has same hashCode() than original", original.hashCode(), clone.hashCode()); 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/test/java/org/jenkinsci/plugins/ParameterizedRemoteTrigger/pipeline/HandleTest.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.ParameterizedRemoteTrigger.pipeline; 2 | 3 | import static org.junit.Assert.assertFalse; 4 | import static org.junit.Assert.assertTrue; 5 | 6 | import org.junit.Test; 7 | 8 | public class HandleTest 9 | { 10 | 11 | @Test 12 | public void testHelp() { 13 | String help = Handle.help(); 14 | //Check only a few to see if it works in general 15 | assertContains(help, true, "- String toString()"); 16 | assertContains(help, true, "- RemoteBuildInfo getBuildInfo()"); 17 | assertContains(help, true, "- RemoteBuildStatus getBuildStatus()"); 18 | assertContains(help, true, "- Result getBuildResult()"); 19 | assertContains(help, true, "- URL getBuildUrl()"); 20 | assertContains(help, true, "- int getBuildNumber()"); 21 | assertContains(help, true, "- boolean isFinished()"); 22 | assertContains(help, false, " set"); 23 | } 24 | 25 | private void assertContains(String help, boolean assertIsContained, String checkString) 26 | { 27 | if(assertIsContained) 28 | assertTrue("Help does not contain '" + checkString + "': \"" + help + "\"", help.contains(checkString)); 29 | else 30 | assertFalse("Help contains '" + checkString + "': \"" + help + "\"", help.contains(checkString)); 31 | } 32 | 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/test/java/org/jenkinsci/plugins/ParameterizedRemoteTrigger/remoteJob/BuildInfoExporterActionTest.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.ParameterizedRemoteTrigger.remoteJob; 2 | 3 | import java.io.IOException; 4 | import java.net.MalformedURLException; 5 | import java.net.URL; 6 | import java.util.Set; 7 | import java.util.concurrent.Callable; 8 | import java.util.concurrent.ExecutionException; 9 | import java.util.concurrent.ExecutorService; 10 | import java.util.concurrent.Executors; 11 | import java.util.concurrent.Future; 12 | import java.util.concurrent.TimeoutException; 13 | 14 | import org.junit.Assert; 15 | import org.junit.Rule; 16 | import org.junit.Test; 17 | import org.jvnet.hudson.test.JenkinsRule; 18 | 19 | import hudson.EnvVars; 20 | import hudson.model.FreeStyleBuild; 21 | import hudson.model.FreeStyleProject; 22 | import hudson.model.ItemGroup; 23 | import hudson.model.Result; 24 | import hudson.model.Run; 25 | import hudson.model.TopLevelItem; 26 | import jenkins.model.Jenkins; 27 | 28 | public class BuildInfoExporterActionTest { 29 | 30 | @Rule 31 | public JenkinsRule jenkinsRule = new JenkinsRule(); 32 | 33 | private final static int PARALLEL_JOBS = 100; 34 | private final static int POOL_SIZE = 50; 35 | 36 | /** 37 | * Same as {@link #testAddBuildInfoExporterAction_parallel()} but sequentially. 38 | * @throws IOException 39 | */ 40 | @Test 41 | public void testAddBuildInfoExporterAction_sequential() throws IOException { 42 | Run parentBuild = new FreeStyleBuild(new FreeStyleProject((ItemGroup) Jenkins.getInstance(), "ParentJob")); 43 | RemoteBuildInfo buildInfo = new RemoteBuildInfo(); 44 | buildInfo.setBuildResult(Result.SUCCESS); 45 | for (int i = 1; i <= PARALLEL_JOBS; i++) { 46 | RemoteBuildInfoExporterAction.addBuildInfoExporterAction(parentBuild, "Job" + i, i, new URL("http://jenkins/jobs/Job" + i), buildInfo); 47 | } 48 | RemoteBuildInfoExporterAction action = parentBuild.getAction(RemoteBuildInfoExporterAction.class); 49 | EnvVars env = new EnvVars(); 50 | action.buildEnvVars(null, env); 51 | checkEnv(env); 52 | } 53 | 54 | /** 55 | * We had ConcurrentModificationExceptions in the past. This test executes {@link RemoteBuildInfoExporterAction#addBuildInfoExporterAction(Run, String, int, URL, BuildStatus)} 56 | * and {@link RemoteBuildInfoExporterAction#buildEnvVars(hudson.model.AbstractBuild, EnvVars)} in parallel to provoke a ConcurrentModificationException (which should not occur anymore). 57 | */ 58 | @Test 59 | public void testAddBuildInfoExporterAction_parallel() throws IOException, InterruptedException, ExecutionException { 60 | Run parentBuild = new FreeStyleBuild(new FreeStyleProject((ItemGroup) Jenkins.getInstance(), "ParentJob")); 61 | ExecutorService executor = Executors.newFixedThreadPool(POOL_SIZE); 62 | 63 | //Start parallel threads adding BuildInfoExporterActions AND one thread reading in parallel 64 | Future[] addFutures = new Future[PARALLEL_JOBS]; 65 | for (int i = 1; i <= PARALLEL_JOBS; i++) { 66 | addFutures[i-1] = executor.submit(new AddActionCallable(parentBuild, i)); 67 | } 68 | Future envFuture = executor.submit(new BuildEnvVarsCallable(parentBuild)); 69 | 70 | //Wait until all finished 71 | while(!isDone(addFutures) && !envFuture.isDone()) sleep(100); 72 | 73 | //Check result 74 | EnvVars env = (EnvVars)envFuture.get(); 75 | checkEnv(env); 76 | } 77 | 78 | 79 | /** 80 | * Sleeps millis millisseconds and swallows any InterruptedExceptions. 81 | * @param millis 82 | */ 83 | private void sleep(int millis) { 84 | try { 85 | Thread.sleep(100); 86 | } catch (InterruptedException e) { 87 | e.printStackTrace(); 88 | } 89 | } 90 | 91 | /** 92 | * Checks if all futures are done. Additionally calls {@link Future#get()} to check if an Exception occured. 93 | * @param addFutures 94 | * @return 95 | * @throws InterruptedException 96 | * @throws ExecutionException 97 | */ 98 | private boolean isDone(Future[] addFutures) throws InterruptedException, ExecutionException { 99 | boolean done = true; 100 | for(Future addFuture : addFutures) { 101 | if(!addFuture.isDone()) { 102 | done = false; 103 | } else { 104 | //Test get to check for exceptions 105 | addFuture.get(); 106 | } 107 | } 108 | return done; 109 | } 110 | 111 | /** 112 | * Checks if the env contains all expected variables 113 | * @param env 114 | */ 115 | private void checkEnv(EnvVars env) { 116 | for(int i = 1; i <= PARALLEL_JOBS; i++) { 117 | Assert.assertEquals("TRIGGERED_BUILD_NUMBERS_Job"+i, ""+i, env.get("TRIGGERED_BUILD_NUMBERS_Job"+i)); 118 | Assert.assertEquals("TRIGGERED_BUILD_NUMBERS_Job"+i, ""+i, env.get("TRIGGERED_BUILD_NUMBERS_Job"+i)); 119 | Assert.assertEquals("TRIGGERED_BUILD_RESULT_Job"+i, "SUCCESS", env.get("TRIGGERED_BUILD_RESULT_Job"+i)); 120 | Assert.assertEquals("TRIGGERED_BUILD_RESULT_Job" + i + "_RUN_"+i, "SUCCESS", env.get("TRIGGERED_BUILD_RESULT_Job" + i + "_RUN_"+i)); 121 | Assert.assertEquals("TRIGGERED_BUILD_RUN_COUNT_Job"+i, "1", env.get("TRIGGERED_BUILD_RUN_COUNT_Job"+i)); 122 | Assert.assertEquals("TRIGGERED_BUILD_URL_Job"+i, "http://jenkins/jobs/Job"+i, env.get("TRIGGERED_BUILD_URL_Job"+i)); 123 | } 124 | } 125 | 126 | /** 127 | * Calls {@link RemoteBuildInfoExporterAction#addBuildInfoExporterAction(Run, String, int, URL, BuildStatus)} a single time. 128 | * This Callable is typically executed multiple tiles in parallel to provoke a ConcurrentModificationException (which should not occur anymore). 129 | */ 130 | private static class AddActionCallable implements Callable { 131 | Run parentBuild; 132 | private int i; 133 | 134 | public AddActionCallable(Run parentBuild, int i) { 135 | this.parentBuild = parentBuild; 136 | this.i = i; 137 | } 138 | 139 | public Boolean call() throws MalformedURLException { 140 | String jobName = "Job" + i; 141 | RemoteBuildInfo buildInfo = new RemoteBuildInfo(); 142 | buildInfo.setBuildResult(Result.SUCCESS); 143 | RemoteBuildInfoExporterAction.addBuildInfoExporterAction(parentBuild, jobName, i, 144 | new URL("http://jenkins/jobs/Job" + i), buildInfo); 145 | System.out.println("AddActionCallable finished for Job" + i); 146 | 147 | RemoteBuildInfoExporterAction action = parentBuild.getAction(RemoteBuildInfoExporterAction.class); 148 | Set projectsWithBuilds = action.getProjectsWithBuilds(); 149 | boolean success = projectsWithBuilds.contains(jobName); 150 | String message = String.format("AddActionCallable %s for %s (projects in list: %s)", 151 | (success ? "was successful " : "failed"), "Job"+i, projectsWithBuilds.size()) ; 152 | System.out.println(message); 153 | if(!success) Assert.fail(message); 154 | return success; 155 | } 156 | } 157 | 158 | /** 159 | * Calls {@link RemoteBuildInfoExporterAction#buildEnvVars(hudson.model.AbstractBuild, EnvVars)} repeatedly until all AddActionCallables finished. 160 | * This way we try to provoke a ConcurrentModificationException (which should not occur anymore). 161 | */ 162 | private static class BuildEnvVarsCallable implements Callable { 163 | Run parentBuild; 164 | 165 | public BuildEnvVarsCallable(Run parentBuild) { 166 | this.parentBuild = parentBuild; 167 | } 168 | 169 | public EnvVars call() throws MalformedURLException, InterruptedException, TimeoutException { 170 | RemoteBuildInfoExporterAction action = parentBuild.getAction(RemoteBuildInfoExporterAction.class); 171 | EnvVars env = new EnvVars(); 172 | long startTime = System.currentTimeMillis(); 173 | while (action == null || action.getProjectsWithBuilds().size() < PARALLEL_JOBS) { 174 | try { 175 | Thread.sleep(100); 176 | } catch (InterruptedException e) {} 177 | action = parentBuild.getAction(RemoteBuildInfoExporterAction.class); 178 | if (action != null) { 179 | //Provoke ConcurrentModificationException 180 | action.buildEnvVars(null, env); 181 | } 182 | if(System.currentTimeMillis() - startTime > 120000) throw new TimeoutException("Only " + action.getProjectsWithBuilds().size() + " of " + PARALLEL_JOBS + " jobs"); 183 | } 184 | action.buildEnvVars(null, env); 185 | return env; 186 | } 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /src/test/java/org/jenkinsci/plugins/ParameterizedRemoteTrigger/remoteJob/BuildInfoTest.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.ParameterizedRemoteTrigger.remoteJob; 2 | 3 | import org.junit.Rule; 4 | import org.junit.Test; 5 | import org.junit.rules.ExpectedException; 6 | 7 | import hudson.model.Result; 8 | 9 | 10 | public class BuildInfoTest { 11 | 12 | @Rule 13 | public ExpectedException thrown = ExpectedException.none(); 14 | 15 | 16 | @Test 17 | public void buildStatusTest() { 18 | 19 | RemoteBuildInfo buildInfo = new RemoteBuildInfo(); 20 | 21 | assert(buildInfo.isNotTriggered()); 22 | assert(buildInfo.getResult() == Result.NOT_BUILT); 23 | } 24 | 25 | @Test 26 | public void illegalBuildStatusTest() { 27 | 28 | thrown.expect(IllegalArgumentException.class); 29 | thrown.expectMessage("It is not possible to set the status to finished without setting the build result. " 30 | + "Please use BuildInfo(Result result) or BuildInfo(String result) in order to set the status to finished."); 31 | 32 | RemoteBuildInfo buildInfo = new RemoteBuildInfo(); 33 | buildInfo.setBuildStatus(RemoteBuildStatus.FINISHED); 34 | } 35 | 36 | @Test 37 | public void buildResultTest() { 38 | 39 | RemoteBuildInfo buildInfo = new RemoteBuildInfo(); 40 | buildInfo.setBuildResult(Result.SUCCESS); 41 | 42 | assert(buildInfo.isFinished()); 43 | assert(buildInfo.getResult() == Result.SUCCESS); 44 | } 45 | 46 | @Test 47 | public void stringBuildResultTest() { 48 | 49 | RemoteBuildInfo buildInfo = new RemoteBuildInfo(); 50 | buildInfo.setBuildResult(Result.SUCCESS); 51 | 52 | assert(buildInfo.isFinished()); 53 | assert(buildInfo.getResult() == Result.SUCCESS); 54 | } 55 | 56 | @Test 57 | public void buildInfoToStringTest() { 58 | 59 | RemoteBuildInfo buildInfo = new RemoteBuildInfo(); 60 | 61 | assert(buildInfo.toString().equals("status=NOT_TRIGGERED")); 62 | 63 | buildInfo = new RemoteBuildInfo(); 64 | buildInfo.setBuildResult(Result.SUCCESS); 65 | 66 | assert(buildInfo.toString().equals("status=FINISHED, result=SUCCESS")); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/test/java/org/jenkinsci/plugins/ParameterizedRemoteTrigger/remoteJob/QueueItemTest.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.ParameterizedRemoteTrigger.remoteJob; 2 | 3 | import hudson.AbortException; 4 | import org.jenkinsci.plugins.ParameterizedRemoteTrigger.ConnectionResponse; 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.junit.runners.Parameterized; 8 | import org.junit.runners.Parameterized.Parameters; 9 | 10 | import java.net.HttpURLConnection; 11 | import java.util.Arrays; 12 | import java.util.Collection; 13 | import java.util.Collections; 14 | import java.util.HashMap; 15 | import java.util.List; 16 | import java.util.Map; 17 | import java.util.stream.Collectors; 18 | 19 | import static org.junit.Assert.assertEquals; 20 | import static org.junit.Assert.assertFalse; 21 | import static org.junit.Assert.assertTrue; 22 | 23 | @RunWith(Parameterized.class) 24 | public class QueueItemTest { 25 | 26 | // QueueItem looks for "Location" so test specifically for that 27 | final static private String key = "Location"; 28 | final static private String id = "4848912"; 29 | final static private String location = String.format("http://example.com/jenkins/my-jenkins1/queue/item/%s/", id); 30 | 31 | // invalid header missing Location 32 | final static private Map> noLocationHeader = new HashMap>() {{ 33 | put("Date", Collections.singletonList("Tue, 21 Apr 2020 02:26:47 GMT")); 34 | put("Server", Collections.singletonList("envoy")); 35 | put(null, Collections.singletonList("HTTP/1.1 201 Created")); 36 | put("Content-Length", Collections.singletonList("0")); 37 | put("X-Envoy-Upstream-Service-Time", Collections.singletonList("15")); 38 | put("X-Content-Type-Options", Collections.singletonList("nosniff")); 39 | }}; 40 | 41 | // Add the Location to make valid header with typical capitalization 42 | final static private Map> locationHeader = new HashMap>(noLocationHeader) {{ 43 | put(key, Collections.singletonList(location)); 44 | }}; 45 | 46 | // valid header with all lowercase. Watch out for null key. 47 | final static private Map> lowerCaseLocationHeader = locationHeader.entrySet().stream().collect( 48 | Collectors.toMap(entry -> entry.getKey() == null ? null : entry.getKey().toLowerCase(), 49 | entry -> entry.getValue())); 50 | 51 | @Parameters 52 | public static Collection data() { 53 | return Arrays.asList(new Object[][] { 54 | { noLocationHeader, false }, 55 | { locationHeader, true }, 56 | { lowerCaseLocationHeader, true } 57 | }); 58 | } 59 | 60 | @Parameterized.Parameter() 61 | public Map> header; 62 | 63 | @Parameterized.Parameter(1) 64 | public boolean isValid; 65 | 66 | @Test 67 | public void test() { 68 | // ConnectionResponse creates case-insensitive map of header 69 | ConnectionResponse connectionResponse = new ConnectionResponse(header, HttpURLConnection.HTTP_OK); 70 | 71 | try { 72 | QueueItem queueItem = new QueueItem(connectionResponse.getHeader()); 73 | assertTrue("QueueItem should have thrown exception for invalid header: " + header, isValid); 74 | assertEquals(queueItem.getLocation(), location); 75 | assertEquals(queueItem.getId(), id); 76 | } catch (AbortException e) { 77 | assertFalse("QueueItem thew exception for valid header: " + header, isValid); 78 | } 79 | } 80 | } -------------------------------------------------------------------------------- /src/test/java/org/jenkinsci/plugins/ParameterizedRemoteTrigger/utils/Base64UtilsTest.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.ParameterizedRemoteTrigger.utils; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | 5 | import org.junit.Test; 6 | 7 | public class Base64UtilsTest { 8 | 9 | @Test 10 | public void testGenAuthNoToken() throws Exception { 11 | String result = Base64Utils.generateAuthorizationHeaderValue(Base64Utils.AUTHTYPE_BASIC, "user", "$password", null, false); 12 | assertEquals("Basic dXNlcjokcGFzc3dvcmQ=", result); 13 | } 14 | 15 | } -------------------------------------------------------------------------------- /src/test/java/org/jenkinsci/plugins/ParameterizedRemoteTrigger/utils/FormValidationUtilsTest.java: -------------------------------------------------------------------------------- 1 | package org.jenkinsci.plugins.ParameterizedRemoteTrigger.utils; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | 5 | import org.junit.Test; 6 | import org.jvnet.hudson.test.WithoutJenkins; 7 | 8 | public class FormValidationUtilsTest 9 | { 10 | 11 | @Test @WithoutJenkins 12 | public void testIsUrl() { 13 | assertEquals(true, FormValidationUtils.isURL("http://xyz")); 14 | assertEquals(true, FormValidationUtils.isURL("https://xyz")); 15 | assertEquals(true, FormValidationUtils.isURL("https://xyz:1234/test")); 16 | assertEquals(false, FormValidationUtils.isURL("xyz")); 17 | assertEquals(false, FormValidationUtils.isURL("")); 18 | assertEquals(false, FormValidationUtils.isURL(null)); 19 | assertEquals(false, FormValidationUtils.isURL("http://")); 20 | assertEquals(false, FormValidationUtils.isURL("https://")); 21 | assertEquals(false, FormValidationUtils.isURL(" http://xyz ")); 22 | assertEquals(false, FormValidationUtils.isURL("http://xyz/$jobPath")); 23 | assertEquals(false, FormValidationUtils.isURL("http://xyz/${jobPath}")); 24 | } 25 | 26 | } 27 | --------------------------------------------------------------------------------