forked from cloudbees/jenkins-scripts
-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathucStoreAndPromoteWithDependencies.groovy
362 lines (329 loc) · 15.1 KB
/
ucStoreAndPromoteWithDependencies.groovy
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
/*** BEGIN META {
"name" : "Store or Promote with dependencies in Custom Update Center",
"comment" : "Calculate required dependencies for a Plugin in Custom Update Center and performs an action (STORE or PROMOTE). The script ",
"parameters" : [pluginName, pluginVersion, updateCenterFullName, updateCenterAction, updateCenterActionDryRun, updateCenterStrategy],
"core": "1.642",
"authors" : [
{ name : "Allan Burdajewicz" }
]
} END META**/
import com.cloudbees.jenkins.updates.data.DependencyEntry
import com.cloudbees.jenkins.updates.data.PluginEntry
import com.cloudbees.jenkins.updates.versioning.VersionNumber
import com.cloudbees.plugins.updatecenter.PluginData
import com.cloudbees.plugins.updatecenter.UpdateCenter
import com.google.common.collect.Sets
/*************************************************
* Variables:
* Change the following variable to suit your need.
* Default configuration provide detailed output
* and do not apply any changes
*************************************************/
/**
* The plugin name.
* Example: 'workflow-aggregator'
*/
String pluginName = "${pluginName}"
/**
* The plugin version. Note: if the EXACT version cannot be found in store or for download, the latest
* available version will be picked by the script.
* This can be controlled with ${updateCenterStrategy} but ONLY higher versions are considered suitable.
* Example: '2.5' or leave empty '' to pick up the latest
*/
String pluginVersion = "${pluginVersion}"
/**
* Full Name of the custom update center.
* Example: 'myFolder/myUc'
*/
String updateCenterFullName = "${updateCenterFullName}"
/**
* Action to undertake after calculating the dependencies.
*
* - 'STORE': Download/Store the plugin with its Dependencies
* - 'PROMOTE': Promote the plugin with its Dependencies
* - 'NOOP' or empty|null: do nothing
*/
String updateCenterAction = "NOOP"
/**
* Control whether to apply the updateCenterAction of just execute a test run:
*
* - true: Print the action to undertake (store or promote)
* - false: Execute the action to undertake
*/
boolean updateCenterActionDryRun = true
/*************************************************
* Further Variables:
* Change the following variable to tweak the
* script's behavior. Default configuration is
* recommended.
*************************************************/
/**
* Strategy for picking available version. When checking version available in store, the script picks the exact version
* if it can find it, otherwise the default behavior is to pick the latest version available. This can be controlled
* with this variable:
*
* - 'LATEST': pick the latest version available in store EVEN IF AN EXACT MATCH IS FOUND
* - 'DEFAULT' or empty|null: if NO EXACT MATCH found for download, pick the latest version found in store (example: checking for 1.2, found 1.3 and 1.5 -> pick 1.5)
*
* Note: With either LATEST / DEFAULT, the output shows all the required dependencies
*/
String updateCenterStoreStrategy = "LATEST"
/**
* Strategy for picking available version. When checking version available for download, the script picks the exact
* version if it can find it, otherwise the default behavior is to pick the latest version available. This can be
* controlled with this variable:
*
* - 'LATEST': pick the latest version available for download EVEN IF AN EXACT MATCH IS FOUND
* - 'DEFAULT' or empty|null: if NO EXACT MATCH found for download, pick the latest version found for download (example: checking for 1.2, found 1.3 and 1.5 -> pick 1.5)
*
* Note: With either LATEST / DEFAULT, the output shows all the required dependencies
*/
String updateCenterDownloadStrategy = "LATEST"
/**
* Fill a map with required dependencies.
* @param updateCenter The update center item
* @param name the name of the plugin
* @param version the version of the plugin
* @param pickStrategy strategy to apply when exact version is not available
* @param checkedDeps the dependencies already checked
* @param optionalDeps the optional dependencies already checked
* @param requiredDeps the required dependencies (to be stored/promoted)
* @param indent indent for log output
*/
boolean fillRequiredDependencies(
UpdateCenter updateCenter,
String name,
String version,
String pickStrategy,
String downloadStrategy,
Collection<DependencyEntry> checkedDeps,
Map<String, VersionNumber> optionalDeps,
Map<String, PluginEntry> requiredDeps,
String indent) {
PluginData pluginData = updateCenter.getPlugin(name)
if(pluginData == null) {
println "${indent}[ERROR]Plugin [${name}] does not exists in this update center!"
return false
}
VersionNumber pluginVersionNumber = version == null ? null : new VersionNumber(version)
println "${indent}[${pluginData.name}:${pluginVersionNumber}]"
PluginEntry pluginStoredEntry, pluginDownloadableEntry = null
println "${indent} Checking available versions in store..."
pluginStoredEntry = getSuitableVersionInStore(pluginData, pluginVersionNumber, pickStrategy,"${indent} ")
if(pluginStoredEntry == null || downloadStrategy == "LATEST") {
println "${indent} Checking available versions for download..."
pluginDownloadableEntry = getSuitableVersionToDownload(pluginData, pluginVersionNumber, downloadStrategy,"${indent} ")
}
PluginEntry pluginEntry = pluginDownloadableEntry != null ? pluginDownloadableEntry : pluginStoredEntry
boolean consistent = true
if(pluginEntry != null) {
println "${indent} [CANDIDATE] Suitable version(s) found [${pluginEntry.name}:${pluginEntry.versionNumber}]"
requiredDeps.put(pluginData.name, pluginEntry)
/* Retrieve required dependencies */
pluginEntry.dependencies.findAll{!checkedDeps.contains(it)}.each { dep ->
checkedDeps.add(dep)
VersionNumber depVersionNumber = new VersionNumber(dep.version)
println "${indent} Checking dependency: \"name\": \"${dep.name}\", \"version\": \"${dep.version}\", \"optional\": \"${dep.getOptional()}\""
/*
* Only pick an optional dependency if it is already promoted. Because the plugin is likely to be installed
* in a client master in which case it is not "optional" anymore.
*/
if(dep.getOptional()) {
// Record the optional dependency requirement
VersionNumber currentOptionalVersion = optionalDeps.get(dep.getName())
if (currentOptionalVersion == null || currentOptionalVersion.isOlderThan(depVersionNumber)) {
optionalDeps.put(dep.getName(), depVersionNumber)
}
if(updateCenter.getPlugin(dep.name)?.versions == null) {
println "${indent} [DISCARD] optional and not in store"
return
} else if(updateCenter.getPlugin(dep.name).getPromotedVersion() == null) {
println "${indent} [DISCARD] optional and not promoted"
return
}
}
/*
* Only check for a dependency if the requirement is higher than what we already found (may
* include optional dependencies)
*/
VersionNumber currentVersionNumber = requiredDeps.get(dep.name)?.versionNumber
Set<VersionNumber> requiredVersions = Sets.newHashSet(currentVersionNumber, optionalDeps.get(dep.getName()), depVersionNumber)
requiredVersions.remove(null)
// Required version is the maximum version of required versions found so far
VersionNumber requiredVersion = Collections.max(requiredVersions)
if (currentVersionNumber == null || currentVersionNumber.isOlderThan(requiredVersion)) {
consistent = fillRequiredDependencies(updateCenter, dep.name, dep.version, pickStrategy,
downloadStrategy, checkedDeps, optionalDeps, requiredDeps,"${indent} ")
} else {
println "${indent} [DISCARD] (Already found a requirement for exact or higher version: ${currentVersionNumber})"
}
}
} else {
println "${indent} [ERROR] No suitable version(s) found in store or for download"
return consistent = false
}
return consistent
}
/**
* Return a suitable stored version of a plugin (one equal or more recent).
* @param updateCenter The update center item
* @param name the name of the plugin
* @param version the version of the plugin
* @param downloadStrategy strategy to apply when exact version is not available
* @param indent indent for log output
* @return The corresponding entry
*/
PluginEntry getSuitableVersionInStore(
PluginData pluginData,
VersionNumber pluginVersionNumber,
String storeStrategy,
String indent) {
PluginEntry toStore = null
//Check for more recent versions
List<PluginEntry> pluginVersionsStored = pluginData.versions.findAll {
entry -> entry.key >= pluginVersionNumber
}.collect {
entry -> entry.value
}
if (pluginVersionsStored == null || pluginVersionsStored.isEmpty()) {
println "${indent}Does not have any suitable version in store"
} else {
toStore = pluginVersionsStored.find { it.versionNumber == pluginVersionNumber}
println "${indent}Requested version ${pluginVersionNumber} ${toStore != null ? "" : "not "}available in store"
if (toStore == null || storeStrategy == "LATEST") {
print "${indent}Found suitable versions in store:"
//Take the latest by default
println " using the latest version ${pluginVersionsStored.get(0).version}"
toStore = pluginVersionsStored.get(0)
}
}
return toStore
}
/**
* Return a suitable download version of a plugin (one equal or more recent).
* @param updateCenter The update center item
* @param name the name of the plugin
* @param version the version of the plugin
* @param downloadStrategy strategy to apply when checking updates
* @param indent indent for log output
* @return The corresponding entry
*/
PluginEntry getSuitableVersionToDownload(
PluginData pluginData,
VersionNumber pluginVersionNumber,
String downloadStrategy,
String indent) {
PluginEntry toDownload = null
List<PluginEntry> pluginUpdates = pluginData.updates.findAll {
entry -> entry.key >= pluginVersionNumber
}.collect {
entry -> entry.value
}
if (pluginUpdates == null || pluginUpdates.isEmpty()) {
println "${indent}Does not have any suitable updates available for download"
} else {
toDownload = pluginUpdates.find { it.versionNumber == pluginVersionNumber}
println "${indent}Requested version ${pluginVersionNumber} ${toDownload != null ? "" : "not"} available for download"
if (toDownload == null || downloadStrategy == "LATEST") {
print "${indent}Found suitable versions for download:"
//Take the latest by default
println " Using the latest version ${pluginUpdates.get(0).version}"
toDownload = pluginUpdates.get(0)
}
}
return toDownload
}
/*************************************************************
* Store With Dependencies: Based on the list calculated above
*************************************************************/
/**
* Download plugin if not already stored
* @param updateCenter the update center item
* @param pluginEntries the list of plugin entries to download
* @return
*/
def downloadPlugins(UpdateCenter updateCenter, Collection<PluginEntry> pluginEntries, boolean dryRun) {
pluginEntries.each { pluginEntry ->
println "[${pluginEntry.name}:${pluginEntry.versionNumber}]"
PluginData pluginData = updateCenter.getPlugin(pluginEntry.name)
if(pluginData.versions.find { entry -> entry.key == pluginEntry.versionNumber} != null) {
println " Is already in store."
} else {
println " Pulling plugin version"
if(!dryRun) {
updateCenter.downloadPlugin(pluginEntry.getUrl(), pluginEntry.name, pluginEntry.version, pluginEntry.sha1)
}
}
}
}
/***************************************************************
* Promote With Dependencies: Based on the list calculated above
***************************************************************/
/**
* Promote plugin if not already promoted
* @param updateCenter the update center item
* @param pluginEntries the list of plugin entries to promote
* @return
*/
def promotePlugins(UpdateCenter updateCenter, Collection<PluginEntry> pluginEntries, boolean dryRun) {
pluginEntries.each { pluginEntry ->
println "[${pluginEntry.name}:${pluginEntry.versionNumber}]"
PluginData pluginData = updateCenter.getPlugin(pluginEntry.name)
if(pluginData.versions.find { entry -> entry.key == pluginEntry.versionNumber} != null) {
if (pluginData.promotedVersionNumber != pluginEntry.versionNumber) {
println " Promoting plugin version"
if(!dryRun) {
pluginData.setPromotedVersion(pluginEntry.version)
}
} else {
println " Is already promoted."
}
// Following can be used to promote to the latest
// pluginData.setPromotedVersion(PluginData.LATEST_VERSION_STRING)
} else {
println " Is not in store. Run the STORE action first"
}
}
}
/*************************************************
* Execution
*************************************************/
UpdateCenter myUC = jenkins.model.Jenkins.instance.getItemByFullName(updateCenterFullName, UpdateCenter.class)
if(myUC == null) {
println "Cannot find UC '${updateCenterFullName}'!"
return
}
println "\n----------------------------------\nCalculate required dependencies:\n----------------------------------"
// Construct the dependencies map
Map<String, PluginEntry> requiredDeps = new TreeMap<>()
boolean isConsistent = fillRequiredDependencies(
myUC,
pluginName,
pluginVersion,
updateCenterStoreStrategy,
updateCenterDownloadStrategy,
new HashSet<DependencyEntry>(),
new HashMap<String, VersionNumber>(),
requiredDeps,
"")
println "\n----------------------------------\nResult\n----------------------------------"
if(!requiredDeps.isEmpty()) {
println "\nRequired Dependencies\n-----------------"
requiredDeps.values().each {
println "${it.getName()}:${it.getVersionNumber()}"
}
}
if(!isConsistent) {
println "\n!!! Found inconsistencies during the calculation of dependencies. Have a look at '[ERROR]' messages) !!!"
if(!updateCenterActionDryRun) {
return
}
}
if(updateCenterAction == "STORE") {
println "\n----------------------------------\nDownload required dependencies:\n----------------------------------"
downloadPlugins(myUC, requiredDeps.values(), updateCenterActionDryRun)
} else if (updateCenterAction == "PROMOTE") {
println "\n----------------------------------\nPromote required dependencies:\n----------------------------------"
promotePlugins(myUC, requiredDeps.values(), updateCenterActionDryRun)
}
return