forked from morficus/Parameterized-Remote-Trigger-Plugin
-
Notifications
You must be signed in to change notification settings - Fork 141
/
Handle.java
380 lines (336 loc) · 13.3 KB
/
Handle.java
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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
package org.jenkinsci.plugins.ParameterizedRemoteTrigger.pipeline;
import static org.apache.commons.lang.StringUtils.isEmpty;
import static org.apache.commons.lang.StringUtils.trimToNull;
import java.io.IOException;
import java.io.Serializable;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.util.Optional;
import edu.umd.cs.findbugs.annotations.CheckForNull;
import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;
import org.apache.commons.lang.StringUtils;
import org.jenkinsci.plugins.ParameterizedRemoteTrigger.BuildContext;
import org.jenkinsci.plugins.ParameterizedRemoteTrigger.RemoteBuildConfiguration;
import org.jenkinsci.plugins.ParameterizedRemoteTrigger.RemoteJenkinsServer;
import org.jenkinsci.plugins.ParameterizedRemoteTrigger.remoteJob.RemoteBuildInfo;
import org.jenkinsci.plugins.ParameterizedRemoteTrigger.remoteJob.RemoteBuildStatus;
import org.jenkinsci.plugins.scriptsecurity.sandbox.whitelists.Whitelisted;
import hudson.model.Result;
import net.sf.json.JSONException;
import net.sf.json.JSONObject;
/**
* A handle to the triggered remote build. This handle is used in Pipelines
* to have direct access to the (correct) remote build instead of relying on
* environment variables (like in a Job). This prevents issues e.g. when triggering
* remote jobs in a parallel pipeline step.
*/
public class Handle implements Serializable {
private static final long serialVersionUID = 4418782245518194292L;
@NonNull
private final RemoteBuildConfiguration remoteBuildConfiguration;
@NonNull
private RemoteBuildInfo buildInfo;
@Nullable
private String jobName;
@Nullable
private String jobFullName;
@Nullable
private String jobDisplayName;
@Nullable
private String jobFullDisplayName;
@Nullable
private String jobUrl;
/**
* The current local Item (Job, Pipeline,...) where this plugin is currently used.
*/
@NonNull
private final String currentItem;
@NonNull
private final RemoteJenkinsServer effectiveRemoteServer;
/*
* The latest log entries from the last called method.
* Unfortunately the TaskListener.getLogger() from the StepContext does
* not write to the pipeline log anymore since the RemoteBuildPipelineStep
* already finished.
* TODO: Once we found a way to log to the pipeline log directly we can switch
*/
@NonNull
private String lastLog;
public Handle(@NonNull RemoteBuildConfiguration remoteBuildConfiguration, @NonNull RemoteBuildInfo buildInfo, @NonNull String currentItem,
@NonNull RemoteJenkinsServer effectiveRemoteServer, @NonNull JSONObject remoteJobMetadata)
{
this.remoteBuildConfiguration = remoteBuildConfiguration;
this.buildInfo = buildInfo;
this.jobName = getParameterFromJobMetadata(remoteJobMetadata, "name");
this.jobFullName = getParameterFromJobMetadata(remoteJobMetadata, "fullName");
this.jobDisplayName = getParameterFromJobMetadata(remoteJobMetadata, "displayName");
this.jobFullDisplayName = getParameterFromJobMetadata(remoteJobMetadata, "fullDisplayName");
this.jobUrl = getParameterFromJobMetadata(remoteJobMetadata, "url");
this.currentItem = currentItem;
this.effectiveRemoteServer = effectiveRemoteServer;
this.lastLog = "";
if(trimToNull(currentItem) == null) throw new IllegalArgumentException("currentItem null");
}
/**
* Check if the remote build is still queued (not building yet).
*
* @return true if still queued, false if already running.
* @throws IOException
* if there is an error retrieving the remote build number.
* @throws InterruptedException
* if any thread has interrupted the current thread.
*/
@Whitelisted
public boolean isQueued() throws IOException, InterruptedException {
return buildInfo.isQueued();
}
/**
* Check if the remote job build is finished.
*
* @return true if the remote job build ran and finished successfully, otherwise false.
* @throws IOException
* if there is an error retrieving the remote build number, or,
* if there is an error retrieving the remote build status, or,
* if there is an error retrieving the console output of the remote build, or,
* if the remote build does not succeed.
* @throws InterruptedException
* if any thread has interrupted the current thread.
*/
@Whitelisted
public boolean isFinished() throws IOException, InterruptedException {
return buildInfo.isFinished();
}
/**
* @return the name or URL of the remote job as configured in the job/pipeline.
*/
public String getConfiguredJobNameOrUrl() {
return remoteBuildConfiguration.getJob();
}
@CheckForNull
public String getJobName()
{
return jobName;
}
@CheckForNull
public String getJobFullName()
{
return jobFullName;
}
@CheckForNull
public String getJobDisplayName()
{
return jobDisplayName;
}
@CheckForNull
public String getJobFullDisplayName()
{
return jobFullDisplayName;
}
@CheckForNull
public String getJobUrl()
{
return jobUrl;
}
/**
* @return the id of the remote job on the queue.
*/
@CheckForNull
public String getQueueId() {
return buildInfo.getQueueId();
}
/**
* Get the build URL of the remote build.
*
* @return the URL, or null if it could not be identified (yet).
*/
@CheckForNull
@Whitelisted
public URL getBuildUrl() {
return buildInfo.getBuildURL() == null ? null : buildInfo.getBuildURL();
}
/**
* Get the build number of the remote build.
*
* @return the build number, or 0 if it could not be identified (yet).
*/
@NonNull
@Whitelisted
public int getBuildNumber() {
return buildInfo.getBuildNumber();
}
/**
* Gets the current build info of the remote job, containing build status and build result.
*
* @return {@link org.jenkinsci.plugins.ParameterizedRemoteTrigger.remoteJob.RemoteBuildInfo} the build info
*/
@NonNull
@Whitelisted
public RemoteBuildInfo getBuildInfo() {
return buildInfo;
}
/**
* Gets the current build status of the remote job.
*
* @return {@link org.jenkinsci.plugins.ParameterizedRemoteTrigger.remoteJob.RemoteBuildStatus} the build status
*/
@NonNull
@Whitelisted
public RemoteBuildStatus getBuildStatus() {
return buildInfo.getStatus();
}
/**
* Updates the current build status of the remote job.
*
* @return {@link org.jenkinsci.plugins.ParameterizedRemoteTrigger.remoteJob.RemoteBuildStatus} the build status
* @throws IOException
* if there is an error retrieving the remote build number, or,
* if there is an error retrieving the remote build status, or,
* if there is an error retrieving the console output of the remote build, or,
* if the remote build does not succeed.
* @throws InterruptedException
* if any thread has interrupted the current thread.
*/
@NonNull
@Whitelisted
public RemoteBuildStatus updateBuildStatus() throws IOException, InterruptedException {
return updateBuildStatus(false);
}
/**
* Updates the build status of the remote build until it is finished.
*
* @return {@link org.jenkinsci.plugins.ParameterizedRemoteTrigger.remoteJob.RemoteBuildStatus} the build status
* @throws IOException
* if there is an error retrieving the remote build number, or,
* if there is an error retrieving the remote build status, or,
* if there is an error retrieving the console output of the remote build, or,
* if the remote build does not succeed.
* @throws InterruptedException
* if any thread has interrupted the current thread.
*/
@NonNull
@Whitelisted
public RemoteBuildStatus updateBuildStatusBlocking() throws IOException, InterruptedException {
return updateBuildStatus(true);
}
@NonNull
private RemoteBuildStatus updateBuildStatus(boolean blockUntilFinished) throws IOException, InterruptedException {
//Return if buildStatus exists and is final (does not change anymore)
if(buildInfo.isFinished()) return buildInfo.getStatus();
PrintStreamWrapper log = new PrintStreamWrapper();
try {
while(!buildInfo.isFinished()) {
BuildContext context = new BuildContext(log.getPrintStream(), effectiveRemoteServer, this.currentItem);
buildInfo = remoteBuildConfiguration.updateBuildInfo(buildInfo, context);
if(!blockUntilFinished) break;
}
return buildInfo.getStatus();
} finally {
lastLog = log.getContent();
}
}
public void setBuildInfo(RemoteBuildInfo buildInfo)
{
this.buildInfo = buildInfo;
}
/**
* Gets the current build result of the remote job.
*
* @return {@link hudson.model.Result} the build result
*/
@NonNull
@Whitelisted
public Result getBuildResult() {
return buildInfo.getResult();
}
/**
* This method returns the log entries which resulted from the last method call
* to the Handle. This is a workaround since logging to the pipeline log directly does
* not work yet if used asynchronously.
*
* @return The latest log entries from the last called method.
*/
@NonNull
@Whitelisted
public String lastLog() {
String log = lastLog.trim();
lastLog = "";
return log;
}
@Whitelisted
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(String.format("Handle [job=%s, remoteServerURL=%s, queueId=%s", remoteBuildConfiguration.getJob(), effectiveRemoteServer.getAddress(), buildInfo.getQueueId()));
sb.append(String.format(", %s", buildInfo.toString()));
if(buildInfo != null) sb.append(String.format(", buildNumber=%s, buildUrl=%s", buildInfo.getBuildNumber(), buildInfo.getBuildURL()));
sb.append("]");
return sb.toString();
}
/**
* This method returns a all available methods. This might be helpful to get available methods
* while developing and testing a pipeline script.
*
* @return a string representing all the available methods.
*/
@Whitelisted
public static String help() {
StringBuilder sb = new StringBuilder();
sb.append("This object provides the following methods:\n");
for (Method method : Handle.class.getDeclaredMethods()) {
if (method.getAnnotation(Whitelisted.class) != null && Modifier.isPublic(method.getModifiers())) {
sb.append("- ").append(method.getReturnType().getSimpleName()).append(" ");
sb.append(method.getName()).append("(");
Class<?>[] params = method.getParameterTypes();
for(Class<?> param : params) {
if(params.length > 1 && !param.equals(params[0])) sb.append( ", ");
sb.append(param.getSimpleName());
}
sb.append(")\n");
}
}
return sb.toString();
}
/**
* This method reads and parses a JSON file which has been archived by the last remote build.
* From Groovy/Pipeline code elements can be accessed directly via object.nodeC.nodeB.leafC.
*
* @param filename
* the filename or path to the remote JSON file relative to the last builds archive folder
* @return JSON structure as Object (consisting of Map, List, and primitive types), or null if not available (yet)
* @throws IOException
* if there is an error identifying the remote host, or
* if there is an error setting the authorization header, or
* if the request fails due to an unknown host, unauthorized credentials, or another reason.
* @throws InterruptedException
* if any thread has interrupted the current thread.
*
*/
@Whitelisted
public Object readJsonFileFromBuildArchive(String filename) throws IOException, InterruptedException {
if(isEmpty(filename)) return null;
URL remoteBuildUrl = getBuildUrl();
URL fileUrl = new URL(remoteBuildUrl, "artifact/" + filename);
PrintStreamWrapper log = new PrintStreamWrapper();
try {
BuildContext context = new BuildContext(log.getPrintStream(), effectiveRemoteServer, this.currentItem);
return remoteBuildConfiguration.doGet(fileUrl.toString(), context, getBuildStatus()).getBody();
} finally {
lastLog = log.getContent();
}
}
@CheckForNull
private String getParameterFromJobMetadata(JSONObject remoteJobMetadata, String string)
{
try {
return Optional.ofNullable(remoteJobMetadata)
.map(meta->meta.getString("name"))
.map(StringUtils::trimToNull)
.orElse(null);
}
catch (JSONException e) {
return null;
}
}
}