-
Notifications
You must be signed in to change notification settings - Fork 35
/
CustomTool.java
339 lines (300 loc) · 12.7 KB
/
CustomTool.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
/*
* Copyright 2012, CloudBees Inc., Synopsys Inc. and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.cloudbees.jenkins.plugins.customtools;
import com.synopsys.arc.jenkinsci.plugins.customtools.CustomToolException;
import com.synopsys.arc.jenkinsci.plugins.customtools.EnvStringParseHelper;
import com.synopsys.arc.jenkinsci.plugins.customtools.LabelSpecifics;
import com.synopsys.arc.jenkinsci.plugins.customtools.PathsList;
import com.synopsys.arc.jenkinsci.plugins.customtools.versions.ToolVersion;
import com.synopsys.arc.jenkinsci.plugins.customtools.versions.ToolVersionConfig;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import hudson.AbortException;
import hudson.EnvVars;
import hudson.Extension;
import hudson.FilePath;
import hudson.model.EnvironmentSpecific;
import hudson.model.JobProperty;
import hudson.model.JobPropertyDescriptor;
import hudson.model.TaskListener;
import hudson.model.Node;
import hudson.remoting.VirtualChannel;
import hudson.slaves.NodeSpecific;
import hudson.tools.ToolDescriptor;
import hudson.tools.ToolInstaller;
import hudson.tools.ToolInstallation;
import hudson.tools.ToolProperty;
import hudson.tools.ZipExtractionInstaller;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Arrays;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import jenkins.MasterToSlaveFileCallable;
import jenkins.plugins.customtools.util.envvars.VariablesSubstitutionHelper;
import org.jenkinsci.Symbol;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.stapler.DataBoundConstructor;
/**
* An arbitrary tool, which can add directories to the build's PATH.
* @author rcampbell
* @author Oleg Nenashev
*
*/
@SuppressFBWarnings(value = "SE_NO_SERIALVERSIONID",
justification = "Actually we do not send the class over the channel. Serial version ID is not required for XStream")
public class CustomTool extends ToolInstallation implements
NodeSpecific<CustomTool>, EnvironmentSpecific<CustomTool> {
/**
* File set includes string like **\/bin These will be added to the PATH
*/
private final @CheckForNull String exportedPaths;
/**
* Label-specific options.
*/
private final @CheckForNull LabelSpecifics[] labelSpecifics;
/**
* A cached value of the home directory.
*/
private transient @CheckForNull String correctedHome = null;
/**
* Optional field, which referenced the {@link ToolVersion} configuration.
*/
private final @CheckForNull ToolVersionConfig toolVersion;
/**
* Additional variables string.
* Stores variables expression in *.properties format.
*/
private final @CheckForNull String additionalVariables;
private static final LabelSpecifics[] EMPTY_LABELS = new LabelSpecifics[0];
@DataBoundConstructor
public CustomTool(@Nonnull String name, @CheckForNull String home,
@CheckForNull List<? extends ToolProperty<?>> properties, @CheckForNull String exportedPaths,
@CheckForNull LabelSpecifics[] labelSpecifics, @CheckForNull ToolVersionConfig toolVersion,
@CheckForNull String additionalVariables) {
super(name, home, properties);
this.exportedPaths = exportedPaths;
this.labelSpecifics = labelSpecifics != null ? Arrays.copyOf(labelSpecifics, labelSpecifics.length) : null;
this.toolVersion = toolVersion;
this.additionalVariables = additionalVariables;
}
public @CheckForNull String getExportedPaths() {
return exportedPaths;
}
/**
* Gets the tool version configuration.
* @return Tool version configuration or null if it is not configured.
*/
public @CheckForNull ToolVersionConfig getToolVersion() {
return toolVersion;
}
public boolean hasVersions() {
return toolVersion != null;
}
@Override
@CheckForNull
public String getHome() {
return (correctedHome != null) ? correctedHome : super.getHome();
}
public void correctHome(@Nonnull PathsList pathList) {
correctedHome = pathList.getHomeDir();
}
public @Nonnull LabelSpecifics[] getLabelSpecifics() {
return (labelSpecifics!=null) ? labelSpecifics : EMPTY_LABELS;
}
/**
* Check if the tool has additional environment variables set.
* @return true when the tool injects additional environment variables.
*/
public boolean hasAdditionalVariables() {
return additionalVariables != null;
}
public @CheckForNull String getAdditionalVariables() {
return additionalVariables;
}
@Override
public CustomTool forEnvironment(EnvVars environment) {
String substitutedHomeDir = VariablesSubstitutionHelper.PATH.resolveVariable(getHome(), environment);
String substitutedPath = VariablesSubstitutionHelper.PATH.resolveVariable(exportedPaths, environment);
String substitutedAdditionalVariables = VariablesSubstitutionHelper.PROP_FILE.resolveVariable(additionalVariables, environment);
return new CustomTool(getName(), substitutedHomeDir,
getProperties().toList(), substitutedPath,
LabelSpecifics.substitute(getLabelSpecifics(), environment),
toolVersion, substitutedAdditionalVariables);
}
@Override
public @Nonnull CustomTool forNode(Node node, TaskListener log)
throws IOException, InterruptedException {
String substitutedHomeDir = VariablesSubstitutionHelper.PATH.resolveVariable(translateFor(node, log), node);
String substitutedPath = VariablesSubstitutionHelper.PATH.resolveVariable(exportedPaths, node);
String substitutedAdditionalVariables = VariablesSubstitutionHelper.PROP_FILE.resolveVariable(additionalVariables, node);
return new CustomTool(getName(), substitutedHomeDir, getProperties().toList(),
substitutedPath, LabelSpecifics.substitute(getLabelSpecifics(), node),
toolVersion, substitutedAdditionalVariables);
}
//FIXME: just a stub
@Deprecated
@Restricted(NoExternalUse.class)
public CustomTool forBuildProperties(Map<JobPropertyDescriptor,JobProperty> properties) {
final String toolHome = getHome();
if (toolHome == null) {
throw new IllegalStateException("Tool home must not be null at this stage, likely it's an API misusage");
}
return new CustomTool(getName(), toolHome, getProperties().toList(),
getExportedPaths(), getLabelSpecifics(),
toolVersion, getAdditionalVariables());
}
/**
* Checks the tool consistency.
* @throws CustomToolException Validation error
*/
public void check() throws CustomToolException {
EnvStringParseHelper.checkStringForMacro("EXPORTED_PATHS", getExportedPaths());
EnvStringParseHelper.checkStringForMacro("HOME_DIR", getHome());
}
/**
* Get list of label specifics, which apply to the specified node.
* @param node Node to be checked
* @return List of the specifics to be applied
* @since 0.3
*/
public @Nonnull List<LabelSpecifics> getAppliedSpecifics(@Nonnull Node node) {
List<LabelSpecifics> out = new LinkedList<>();
if (labelSpecifics != null) {
for (LabelSpecifics spec : labelSpecifics) {
if (spec.appliesTo(node)) {
out.add(spec);
}
}
}
return out;
}
@Extension
@Symbol("custom")
public static class DescriptorImpl extends ToolDescriptor<CustomTool> {
public DescriptorImpl() {
load();
}
@Override
public String getDisplayName() {
return Messages.CustomTool_DescriptorImpl_DisplayName();
}
@Override
public void setInstallations(CustomTool... installations) {
super.setInstallations(installations);
save();
}
/**
* Gets a {@link CustomTool} by its name.
* @param name A name of the tool to be retrieved.
* @return A {@link CustomTool} or null if it has no found
*/
public @CheckForNull CustomTool byName(String name) {
for (CustomTool tool : getInstallations()) {
if (tool.getName().equals(name)) {
return tool;
}
}
return null;
}
@Override
public List<? extends ToolInstaller> getDefaultInstallers() {
return Collections.singletonList(new ZipExtractionInstaller(null,
null, null));
}
}
/**
* Finds the directories to add to the path, for the given node.
* Uses Ant filesets to expand the patterns in the exportedPaths field.
*
* @param node where the tool has been installed
* @return a list of directories to add to the $PATH
*
* @throws IOException Operation error
* @throws InterruptedException Operation has been interrupted
*/
protected @Nonnull PathsList getPaths(@Nonnull Node node) throws IOException, InterruptedException {
final String toolHome = getHome();
if (toolHome == null) {
throw new FileNotFoundException("Cannot retrieve home directory of the custom tool " + getName());
}
FilePath homePath = new FilePath(node.getChannel(), toolHome);
//FIXME: Why?
if (exportedPaths == null) {
return PathsList.EMPTY;
}
final List<LabelSpecifics> specs = getAppliedSpecifics(node);
return homePath.act(new GetPaths(specs, exportedPaths, toolHome));
}
private static class GetPaths extends MasterToSlaveFileCallable<PathsList> {
private final List<LabelSpecifics> specs;
private final @CheckForNull String exportedPaths;
private final @CheckForNull String toolHome;
GetPaths(List<LabelSpecifics> specs, String exportedPaths, String toolHome) {
this.specs = specs;
this.exportedPaths = exportedPaths;
this.toolHome = toolHome;
}
private void parseLists(String pathList, List<String> target) {
String[] items = pathList.split("\\s*,\\s*");
for (String item : items) {
if (item.isEmpty()) {
continue;
}
target.add(item);
}
}
@Override
public PathsList invoke(File f, VirtualChannel channel)
throws IOException, InterruptedException {
// Construct output paths
List<String> items = new LinkedList<>();
if (exportedPaths != null) {
parseLists(exportedPaths, items);
}
for (LabelSpecifics spec : specs) {
final String exportedPathsFromSpec = spec.getExportedPaths();
if (exportedPathsFromSpec != null) {
parseLists(exportedPathsFromSpec, items);
}
}
// Resolve exported paths
List<String> outList = new LinkedList<>();
for (String item : items) {
File file = new File(item);
if (!file.isAbsolute()) {
file = new File(toolHome, item);
}
// Check if directory exists
if (!file.isDirectory() || !file.exists()) {
throw new AbortException("Wrong EXPORTED_PATHS configuration. Can't find "+file.getPath());
}
outList.add(file.getAbsolutePath());
}
// Resolve home dir
if (toolHome == null) {
throw new IOException("Cannot retrieve Tool home directory. Should never happen ant this stage, please file a bug");
}
final File homeDir = new File(toolHome);
return new PathsList(outList, homeDir.getAbsolutePath());
}
}
}