-
Notifications
You must be signed in to change notification settings - Fork 0
/
jenkins-universal-wrapper-pipeline.groovy
1989 lines (1902 loc) · 123 KB
/
jenkins-universal-wrapper-pipeline.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
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/usr/bin/env groovy
/**
* Jenkins Universal Wrapper Pipeline v1.0.1
* (c) Alexander Bazhenov, 2023-2024
*
* This Source Code Form is subject to the terms of the Apache License v2.0.
* If a copy of this source file was not distributed with this file, You can obtain one at:
* https://github.com/alexanderbazhenoff/jenkins-universal-wrapper-pipeline/blob/main/LICENSE
*/
import groovy.text.StreamingTemplateEngine
// groovylint-disable-next-line MethodCount
@Library('jenkins-shared-library')
/**
* Repo URL and a branch of 'universal-wrapper-pipeline-settings' to load current pipeline settings, e.g:
* '[email protected]:alexanderbazhenoff/ansible-wrapper-settings.git'. Will be ignored when SETTINGS_GIT_BRANCH
* pipeline parameter present and not blank.
*/
final String SettingsGitUrl = 'http://github.com/alexanderbazhenoff/universal-wrapper-pipeline-settings'
final String DefaultSettingsGitBranch = 'main'
/** Prefix for pipeline settings relative path inside the 'universal-wrapper-pipeline-settings' project, that will be
* added automatically on yaml load.
*/
final String SettingsRelativePathPrefix = 'settings'
/** Jenkins pipeline name regex, a string that will be cut from pipeline name to become a filename of yaml pipeline
* settings to be loaded. Example: Your jenkins pipeline name is 'prefix_pipeline-name_postfix'. To load pipeline
* settings 'pipeline-name.yml' you can use regex list: ['^prefix_','_postfix$']. All pipeline name prefixes should be
* useful to split your jenkins between your company departments (e.g: 'admin', 'devops, 'qa', 'develop', etc...), while
* postfixes are useful to mark pipeline as a changed version of original.
*/
final List PipelineNameRegexReplace = ['^(admin|devops|qa)_']
/** Ansible installation name from jenkins Global Configuration Tool, otherwise leave them empty for defaults from
* Jenkins Shared Library.
*/
final String AnsibleInstallationName = 'home_local_bin_ansible'
/** Built-in pipeline parameters, which are mandatory and not present in 'universal-wrapper-pipeline-settings'. */
final List BuiltinPipelineParameters = [
[name : 'UPDATE_PARAMETERS',
type : 'boolean',
default : false,
description: 'Update pipeline parameters from settings file only.'],
[name : 'SETTINGS_GIT_BRANCH',
type : 'string',
regex : '(\\*)? +(.*?) +(.*?)? ((\\[(.*?)(: (.*?) (\\d+))?\\])? ?(.*$))?',
description: 'Git branch of ansible-wrapper-settings project (to override defaults on development).'],
[name : 'NODE_NAME',
type : 'string',
description: 'Name of Jenkins node to run directly on.'],
[name : 'NODE_TAG',
type : 'string',
default : 'ansible210',
description: 'Run on Jenkins node with specified tag.'],
[name : 'DRY_RUN',
type : 'boolean',
description: String.format('%s (%s).', 'Dry run mode to use for pipeline settings troubleshooting',
'will be ignored on pipeline parameters needs to be injected')],
[name: 'DEBUG_MODE',
type: 'boolean']
]
/**
* Get printable key value from map item (e.g. printable 'name' key which convertible to string).
*
* @param mapItem - map item to get printable key value from.
* @param keyName - key name to get.
* @param nameOnUndefined - printable value when key is absent or not convertible to string.
* @return - printable key value.
*/
static String getPrintableValueKeyFromMapItem(Map mapItem, String keyName = 'name',
String nameOnUndefined = '<undefined>') {
mapItem && mapItem.containsKey(keyName) && detectIsObjectConvertibleToString(mapItem.get(keyName)) ?
mapItem.get(keyName).toString() : nameOnUndefined
}
/**
* Detect is pipeline parameter item probably 'choice' type.
*
* @param paramItem - pipeline parameter item to detect.
* @return - true when 'choice'.
*/
static Boolean detectPipelineParameterItemIsProbablyChoice(Map paramItem) {
paramItem.containsKey('choices') && paramItem.get('choices') instanceof ArrayList
}
/**
* Detect is pipeline parameter item probably 'boolean' type.
*
* @param paramItem - pipeline parameter item to detect.
* @return - true when 'boolean'.
*/
static Boolean detectPipelineParameterItemIsProbablyBoolean(Map paramItem) {
paramItem.containsKey('default') && paramItem.get('default') instanceof Boolean
}
/**
* Find non-empty map items from list.
*
* @param map - map to find items from.
* @param keysToCollect - list of keys that needs to be found.
* @return - only map items specified in listOfKeysToCollect.
*/
static Map findMapItemsFromList(Map map, List keysToCollect) {
map.findAll { mapKey, mapVal -> keysToCollect.contains(mapKey) && mapVal && mapVal?.toString()?.trim() }
}
/**
* Array List to string separated with commas (optional last one by 'and').
*
* @param arrayListItems - arrayList to convert items from.
* @param splitLastByAnd - when true separated the last item with 'and' word.
* @return - string with arrayList items.
*/
static String arrayListToReadableString(List arrayListItems, Boolean splitLastByAnd = true) {
String strByCommas = arrayListItems.toString().replaceAll(',\\s', "', '").replaceAll('[\\[\\]]', "'")
splitLastByAnd && arrayListItems?.size() > 1 ? String.format('%s and %s',
strByCommas.substring(0, strByCommas.lastIndexOf(", '")),
strByCommas.substring(strByCommas.lastIndexOf(", '") + 2, strByCommas.length())) : strByCommas
}
/**
* Convert map items to string separated with commas (optional last one by 'and').
*
* @param map - map to convert key names from.
* @param keyNames - when true format key names from the map, otherwise format values.
* @param splitLastByAnd - when true separated the last item with 'and' word.
* @return - string with key names.
*/
static String mapItemsToReadableListString(Map map, Boolean keyNames = true, Boolean splitLastByAnd = true) {
arrayListToReadableString(keyNames ? map.keySet() as ArrayList : map.values() as ArrayList, splitLastByAnd)
}
/**
* Check environment variable name match POSIX shell standards.
*
* @param name - variable name to check regex match.
* @return - true when match.
*/
static Boolean checkEnvironmentVariableNameCorrect(Object name) {
detectIsObjectConvertibleToString(name) && name.toString().matches('[a-zA-Z_]+[a-zA-Z0-9_]*')
}
/**
* Detect if an object will be human readable string after converting to string (exclude lists, maps, etc).
*
* @param obj - object to detect.
* @return - true when object is convertible to human readable string.
*/
static Boolean detectIsObjectConvertibleToString(Object obj) {
(obj instanceof String || obj instanceof Integer || obj instanceof Float || obj instanceof BigInteger)
}
/**
* Detect if an object will be correct after conversion to boolean.
*
* @param obj - object to detect.
* @return - true when object will be correct.
*/
static Boolean detectIsObjectConvertibleToBoolean(Object obj) {
(obj?.toBoolean()).toString() == obj?.toString()
}
/**
* Get pipeline parameter name from pipeline parameter config item and check this pipeline parameter emptiness state
* (defined or empty).
*
* @param paramItem - pipeline parameter item map (which is a part of parameter settings) to get parameter name.
* @param pipelineParameters - pipeline parameters for current job build (actually requires a pass of 'params' which is
* class java.util.Collections$UnmodifiableMap).
* @param envVariables - environment variables for current job build (actually requires a pass of 'env' which is
* class org.jenkinsci.plugins.workflow.cps.EnvActionImpl).
* @param isUndefined - condition to check state: set true to detect pipeline parameter for current job is undefined, or
* false to detect parameter is defined.
* @return - arrayList of:
* - printable parameter name (or '<undefined>' when name wasn't set);
* - return true when condition specified in 'isUndefined' method variable met.
*/
static List getPipelineParamNameAndDefinedState(Map paramItem, Object pipelineParameters, Object envVariables,
Boolean isUndefined = true) {
[getPrintableValueKeyFromMapItem(paramItem), (paramItem.get('name') && pipelineParameters
.containsKey(paramItem.name) && isUndefined ^ (envVariables[paramItem.name as String]?.trim()).asBoolean())]
}
/**
* Extract parameters arrayList from pipeline settings map (without 'required' and 'optional' map structure).
*
* @param pipelineSettings - pipeline settings map.
* @param builtinPipelineParameters - additional built-in pipeline parameters arrayList.
* @return - pipeline parameters arrayList.
*/
static List extractParamsListFromSettingsMap(Map pipelineSettings, List builtinPipelineParameters) {
(pipelineSettings.get('parameters')) ? (pipelineSettings.parameters.get('required') ?: []) +
(pipelineSettings.parameters.get('optional') ?: []) + builtinPipelineParameters : []
}
/**
* Get Boolean variable enabled state from environment variables.
*
* @param env - environment variables for current job build (actually requires a pass of 'env').
* @param variableName - Environment variable to get.
* @return - true when enabled.
*/
static Boolean getBooleanVarStateFromEnv(Object envVariables, String variableName = 'DEBUG_MODE') {
envVariables.getEnvironment().get(variableName)?.toBoolean() // groovylint-disable-line UnnecessaryGetter
}
/**
* Get Boolean Pipeline parameter state from params object.
*
* @param pipelineParams - pipeline parameters for current job build (actually requires a pass of 'params').
* @param parameterName - parameter name.
* @return - true when enabled.
*/
static Boolean getBooleanPipelineParamState(Object pipelineParams, String parameterName = 'DRY_RUN') {
pipelineParams.get(parameterName)?.toBoolean()
}
/**
* Incompatible keys in map found error message wrapper.
*
* @param keys - arrayList of: First keyName, second keyName, etc...
* @param keyDescriptionMessagePrefix - just a prefix for an error message what keys are incompatible.
* @param onlyOneOfThem - just a postfix message that means only one key required.
* @return - formatted error message.
*/
static String incompatibleKeysMsgWrapper(List keysForMessage, String keyDescriptionMessagePrefix = 'Keys',
Boolean onlyOneOfThem = true) {
String.format('%s %s are incompatible.%s', keyDescriptionMessagePrefix, arrayListToReadableString(keysForMessage),
onlyOneOfThem ? ' Please define only one of them.' : '')
}
/**
* Get dry-run state and pipeline action message.
*
* @param envVariables - environment variables for current job build (actually requires a pass of 'env').
* @param actionName - action name just to print.
* @param printableActionLinkItem - action link item.
* @param actionLinkItemKeysFilter - keys to filter from action link item (required keys for the action).
* @return - arrayList of:
* - true when dry-run is enabled;
* - action message to print before action run.
*/
static List getDryRunStateAndActionMsg(Object envVariables, String actionName, Map printableActionLinkItem,
List actionLinkItemKeysFilter) {
Boolean dryRunAction = getBooleanVarStateFromEnv(envVariables, 'DRY_RUN')
Map printableActionLinkItemTemp = findMapItemsFromList(printableActionLinkItem, actionLinkItemKeysFilter)
String actionMsgDetails = String.format(' %s', printableActionLinkItemTemp.size() > 0 ?
printableActionLinkItemTemp.toString() : '')
[dryRunAction, String.format('%s%s%s', dryRunAction ? 'dry-run of ' : '', actionName, actionMsgDetails)]
}
/**
* Update environment variables from map keys (e.g. universalPipelineWrapperBuiltIns).
*
* @param mapToUpdateFrom - map to update from.
* @param envVariables - environment variables for current job build (actually requires a pass of 'env').
* @return - updated environment variables.
*/
static Object updateEnvFromMapKeys(Map mapToUpdateFrom, Object envVariables) {
mapToUpdateFrom.each { mapToUpdateFromKey, mapToUpdateFromValue ->
envVariables[mapToUpdateFromKey.toString()] = mapToUpdateFromValue.toString()
}
envVariables
}
/**
* Get map sub-key wrapper.
*
* @param subKeyNameToGet - sub-key name to get (e.g. action link to get item from 'action' key of pipeline settings).
* @param map - map to get sub-key from (e.g. pipeline settings).
* @param keyNameToGetFrom - key name to get sub-key from (e.g. 'action').
* @return - arrayList of:
* - true getting sub-key successfully done;
* - sub-key item data (e.g. action link item).
*/
static List getMapSubKey(String subKeyNameToGet, Map mapToGetFrom, String keyNameToGetFrom = 'actions') {
Boolean subKeyDefined = (subKeyNameToGet && mapToGetFrom?.get(keyNameToGetFrom) &&
mapToGetFrom.get(keyNameToGetFrom)?.containsKey(subKeyNameToGet))
[subKeyDefined, subKeyDefined ? mapToGetFrom.get(keyNameToGetFrom)?.get(subKeyNameToGet) : [:]]
}
/**
* Map to formatted string table with values replacement.
*
* @param sourceMap - source map to create text table from.
* @param replaceKeyName - key name in source map to perform value replacement.
* @param regexItems - list of regex items to apply for value replacement.
* @param replaceItems - list of items for value replacement to replace with. List must be the same length as a
* regexItemsList, otherwise will be replaced with empty line ''
* @param formattedTable - pass a table header here.
* @return - formatted string table results.
*/
String mapToFormattedStringTable(Map sourceMap, String replaceKeyName = 'state', List regexItems = ['true', 'false'],
List replaceItems = ['[PASS]', '[FAIL]'], String formattedTable = '') {
def (Boolean createTable, Map tableColumnSizes) = [false, [:]]
for (Integer i = 0; i < 2; i++) {
sourceMap.each { sourceMapEntry ->
sourceMapEntry.value.each { k, v ->
String tableEntry = (replaceKeyName?.trim() && k == replaceKeyName) ?
CF.applyReplaceRegexItems(v.toString(), regexItems, replaceItems) : v.toString()
tableColumnSizes[k] = [tableColumnSizes?.get(k), tableEntry.length() + 2].max()
Integer padSize = tableColumnSizes[k] - tableEntry.length()
formattedTable += createTable ? String.format('%s%s', tableEntry, ' ' * padSize) : ''
}
formattedTable += createTable ? '\n' : ''
}
createTable = !createTable
}
formattedTable
}
/**
* Clone 'universal-wrapper-pipeline-settings' from git repository, load yaml pipeline settings and return them as map.
*
* @param settingsGitUrl - git repo URL to clone from.
* @param settingsGitBranch - git branch.
* @param settingsRelativePath - relative path inside the 'universal-wrapper-pipeline-settings' project.
* @param printYaml - if true output 'universal-wrapper-pipeline-settings' content on a load.
* @param workspaceSubFolder - sub-folder in jenkins workspace where the git project will be cloned.
* @return - map with pipeline settings.
*/
Map loadPipelineSettings(String settingsGitUrl, String settingsGitBranch, String settingsRelativePath,
Boolean printYaml = true, String workspaceSubFolder = 'settings') {
CF.cloneGitToFolder(settingsGitUrl, settingsGitBranch, workspaceSubFolder)
String pathToLoad = String.format('%s/%s', workspaceSubFolder, settingsRelativePath)
if (printYaml) CF.outMsg(0, String.format('Loading pipeline settings:\n%s', readFile(pathToLoad)))
readYaml(file: pathToLoad)
}
/**
* Verify all required jenkins pipeline parameters are presents.
*
* @param pipelineParams - jenkins built-in 'params' UnmodifiableMap variable with current build pipeline parameters.
* @param currentPipelineParams - an arrayList of map items to check, e.g: [map_item1, map_item2 ... map_itemN].
* While single map item format is:
* [
* name: 'PARAMETER_NAME',
* type: 'string|text|choice|boolean|password'
* default: 'default_value',
* choices: ['one', 'two', 'three'],
* description: 'Your jenkins parameter pipeline description.',
* trim: false|true
* ]
*
* Please note:
* - 'choices' key is only for 'type: choice'.
* - 'default' key is for all types except 'type: choice'. This key is incompatible with
* 'type: choice'. For other types this key is optional: it's will be false for boolean,
* and '' (empty line) for string, password and text parameters.
* - 'trim' key is available for 'type: string'. This key is optional, by default it's
* false.
* - 'description' key is optional, by default it's '' (empty line).
*
* More info in Configuration files format description in 'UNIVERSAL WRAPPER PIPELINE
* SETTINGS' project.
* @return - arrayList of:
* - true when jenkins pipeline parameters update required;
* - true when no errors.
*/
List verifyPipelineParamsArePresents(List requiredParams, Object currentPipelineParams) {
def (Boolean updateParamsRequired, Boolean verifyPipelineParamsOk) = [false, true]
String ignoreMsg = 'Skipping parameter from pipeline settings'
String keyValueIncorrectMsg = 'key for pipeline parameter is undefined or incorrect value specified'
requiredParams.each {
Boolean paramNameConvertibleToString = detectIsObjectConvertibleToString(it.get('name'))
Boolean paramNamingCorrect = checkEnvironmentVariableNameCorrect(it.get('name'))
if (!it.get('type') && !detectPipelineParameterItemIsProbablyChoice(it as Map) &&
!detectPipelineParameterItemIsProbablyBoolean(it as Map)) {
CF.outMsg(3, String.format("%s: '%s' %s.", 'Parameter from pipeline settings might be ignored', 'type',
keyValueIncorrectMsg))
verifyPipelineParamsOk = false
}
if (!it.get('name') || !paramNameConvertibleToString || !paramNamingCorrect) {
String pipelineParamPosixMsg = String.format("%s: '%s' %s%s", ignoreMsg, 'name', keyValueIncorrectMsg,
paramNameConvertibleToString && !paramNamingCorrect ?
" (parameter name didn't met POSIX standards)." : '.')
verifyPipelineParamsOk = errorMsgWrapper(true, true, 3, pipelineParamPosixMsg)
} else if (it.get('name') && !currentPipelineParams.containsKey(it.get('name'))) {
updateParamsRequired = true
}
}
[updateParamsRequired, verifyPipelineParamsOk]
}
/**
* Convert pipeline settings map item and add to jenkins pipeline parameters.
*
* @param item - pipeline settings map item to convert.
* @return - jenkins pipeline parameters.
*/
List pipelineSettingsItemToPipelineParam(Map item) {
List param = []
String defaultString = item.containsKey('default') ? item.default.toString() : ''
String description = item.containsKey('description') ? item.description : ''
if (item.get('name') && detectIsObjectConvertibleToString(item.get('name')) &&
checkEnvironmentVariableNameCorrect(item.get('name'))) {
if (item.get('type') == 'choice' || detectPipelineParameterItemIsProbablyChoice(item))
param += [choice(name: item.name, choices: item.choices.each { it.toString() }, description: description)]
if (item.get('type') == 'boolean' || detectPipelineParameterItemIsProbablyBoolean(item))
param += [booleanParam(name: item.name, description: description,
defaultValue: item.containsKey('default') ? item.default.toBoolean() : false)]
if (item.get('type') == 'password')
param += [password(name: item.name, defaultValue: defaultString, description: description)]
if (item.get('type') == 'text')
param += [text(name: item.name, defaultValue: defaultString, description: description)]
if (item.get('type') == 'string')
param += [string(name: item.name, defaultValue: defaultString, description: description,
trim: item.containsKey('trim') ? item.trim : false)]
}
param
}
/**
* Format and print error of pipeline settings item check.
*
* @param eventNum - event number to output: 3 is an ERROR, 2 is a WARNING.
* @param itemName - pipeline settings item name to output.
* @param errorMsg - details of error to output.
* @param enableCheck - when true enable message and possible return status changes.
* @param currentState - current overall state value to return from function: false when previous items contains an
* error(s).
* @return - 'false' as a failed pipeline check item state.
*/
Boolean pipelineSettingsItemError(Integer eventNum, String itemName, String errorMsg, Boolean enableCheck = true,
Boolean currentState = true) {
errorMsgWrapper(enableCheck, currentState, eventNum, String.format("Wrong syntax in pipeline parameter '%s': %s.",
itemName, errorMsg))
}
/**
* Structure or action execution error or warning message wrapper.
*
* @param enableCheck - true on check mode, false on execution to skip checking.
* @param state - current state to pass or change (true when ok), e.g: a state of a structure for the whole item, or
* current state of item execution, etc...
* @param eventNum - event number: 3 is an error, 2 is a warning.
* @param msg - error or warning message.
* @return - current state return.
*/
Boolean errorMsgWrapper(Boolean enableCheck, Boolean state, Integer eventNum, String msg) {
if (enableCheck) CF.outMsg(eventNum, msg)
(enableCheck && eventNum == 3) ? false : state
}
/**
* Check pipeline parameters in pipeline settings item for correct keys structure, types and values.
*
* @param item - pipeline settings item to check.
* @return - pipeline parameters item check status (true when ok).
*/
Boolean pipelineParametersSettingsItemCheck(Map item) {
String printableParameterName = getPrintableValueKeyFromMapItem(item)
CF.outMsg(0, String.format("Checking pipeline parameter '%s':\n%s", printableParameterName, CF.readableMap(item)))
/** Check 'name' key is present and valid. */
Boolean checkOk = pipelineSettingsItemError(3, printableParameterName, 'Invalid parameter name',
item.containsKey('name') && !checkEnvironmentVariableNameCorrect(item.get('name')), true)
checkOk = pipelineSettingsItemError(3, printableParameterName, "'name' key is required, but undefined",
!item.containsKey('name'), checkOk)
/** When 'assign' sub-key is defined inside 'on_empty' key, checking it's correct. */
String pipeParamSettingsAssignErrMsg = String.format("%s: '%s'", 'Unable to assign due to incorrect variable name',
item.get('on_empty')?.get('assign'))
Boolean pipeParamSettingsAssignEnableCheck = item.get('on_empty') &&
item.on_empty.get('assign') instanceof String && item.on_empty.assign.startsWith('$')
checkOk = pipelineSettingsItemError(3, printableParameterName, pipeParamSettingsAssignErrMsg,
pipeParamSettingsAssignEnableCheck && !checkEnvironmentVariableNameCorrect(item.on_empty.assign.toString()
.replaceAll('[\${}]', '')), checkOk) // groovylint-disable-line GStringExpressionWithinString
if (item.containsKey('type')) {
/** Check 'type' value with other keys data type mismatch. */
String msg = item.type == 'choice' && !item.containsKey('choices') ?
"'type' set as choice while no 'choices' list defined" : ''
if (item.type == 'boolean' && item.containsKey('default') && !(item.default instanceof Boolean))
msg = String.format("'type' set as boolean while 'default' key is not. It's %s%s",
item.get('default').getClass().toString().tokenize('.').last().toLowerCase(),
detectPipelineParameterItemIsProbablyBoolean(item) ? ", but it's convertible to boolean" : '')
checkOk = pipelineSettingsItemError(3, printableParameterName, msg, msg.trim() as Boolean, checkOk)
} else {
/** Try to detect 'type' when not defined. */
List autodetectData = detectPipelineParameterItemIsProbablyBoolean(item) ? ['default', 'boolean'] : []
autodetectData = detectPipelineParameterItemIsProbablyChoice(item) ? ['choices', 'choice'] : autodetectData
/** Output reason and 'type' key when autodetect is possible. */
if (autodetectData) {
checkOk = pipelineSettingsItemError(3, printableParameterName, String.format("%s by '%s' key: %s",
"'type' key is not defined, but was detected", autodetectData[0], autodetectData[1]))
} else {
String msg = item.containsKey('default') && detectIsObjectConvertibleToString(item.default) ?
". Probably 'type' is password, string or text" : ''
checkOk = pipelineSettingsItemError(3, printableParameterName, String.format('%s%s',
"'type' is required, but wasn't defined", msg))
}
}
/** Check 'default' and 'choices' keys incompatibility and 'choices' value. */
checkOk = pipelineSettingsItemError(3, printableParameterName, "'default' and 'choices' keys are incompatible",
item.containsKey('choices') && item.containsKey('default'), checkOk)
pipelineSettingsItemError(3, printableParameterName, "'choices' value is not a list of items",
item.containsKey('choices') && !(item.get('choices') instanceof ArrayList), checkOk)
}
/**
* Inject parameters to current jenkins pipeline.
*
* @param requiredParams - arrayList of jenkins pipeline parameters to inject, e.g:
* [
* [choice(name: 'PARAM1', choices: ['one', 'two'], description: 'description1')],
* [string(name: 'PARAM2', defaultValue: 'default', description: 'description2')]
* ]
* etc... Check pipelineSettingsItemToPipelineParam() function for details.
* @param finishWithFail - when true finish with success parameters injection. Otherwise, with fail.
* @param currentPipelineParams - pipeline parameters for current job build (actually requires a pass of 'params'). When
* currentPipelineParams.DRY_RUN is 'true' pipeline parameters update won't be performed.
*/
// groovylint-disable-next-line MethodReturnTypeRequired, NoDef
def updatePipelineParams(List requiredParams, Boolean finishWithSuccess, Object currentPipelineParams) {
def (Boolean dryRun, List newPipelineParams) = [getBooleanPipelineParamState(currentPipelineParams), []]
currentBuild.displayName = String.format('pipeline_parameters_update--#%s%s', env.BUILD_NUMBER, dryRun ?
'-dry_run' : '')
requiredParams.each { newPipelineParams += pipelineSettingsItemToPipelineParam(it as Map) }
if (!dryRun)
properties([parameters(newPipelineParams)])
if (finishWithSuccess) {
List msgArgs = dryRun ? ["n't", 'Disable dry-run mode'] : [' successfully', "Select 'Build with parameters'"]
CF.outMsg(2, String.format('Pipeline parameters was%s injected. %s and run again.', msgArgs[0], msgArgs[1]))
CF.interruptPipelineOk(3)
} else {
error 'Pipeline parameters injection failed. Check pipeline config and run again.'
}
}
/**
* Check pipeline parameters format as an arrayList of pipeline settings key: map items for key structure, types and
* values.
*
* @param parameters - all pipeline parameters to check.
* @return - the whole pipeline parameters check status (true when ok).
*/
Boolean checkPipelineParamsFormat(List parameters) {
Boolean allPass = true
parameters.each {
allPass = pipelineParametersSettingsItemCheck(it as Map) ? allPass : false
}
allPass
}
/**
* Handle pipeline parameter assignment when 'on_empty' key defined in pipeline settings item.
*
* @param settingsItem - settings item from pipeline settings to handle.
* @param envVariables - environment variables for current job build (actually requires a pass of 'env').
* @return - arrayList of:
* - true when pipeline parameter needs an assignment;
* - true when pipeline parameter assignment successfully done or skipped, otherwise false;
* - string with assigned environment variable(s);
* - true when needs to count an error when pipeline parameter undefined and can't be assigned;
* - true when needs to warn when pipeline parameter undefined and can't be assigned.
*/
List handleAssignmentWhenPipelineParamIsUnset(Map settingsItem, Object envVariables) {
if (!settingsItem.get('on_empty'))
return [false, true, '', true, false]
Boolean fail = settingsItem.on_empty.get('fail') ? settingsItem.on_empty.get('fail').asBoolean() : true
Boolean warn = settingsItem.on_empty.get('warn').asBoolean()
if (!settingsItem.on_empty.get('assign'))
return [false, true, '', fail, warn]
def (Boolean assignmentIsPossible, Boolean assignmentOk, String assignment) = getTemplatingFromVariables(
settingsItem.on_empty.assign.toString(), envVariables)
[assignmentIsPossible, assignmentOk, assignment, fail, warn]
}
/**
* Template of assign variables inside string.
*
* @param assignment - string that probably contains environment variable(s) (e.g. '$FOO' or '$FOO somewhat $BAR text').
* @param envVariables - environment variables for current job build (actually requires a pass of 'env').
* @param additionalVariablesBinding - additional (or non-environment) variables for templating.
* @return - arrayList of:
* - true when assignment is possible;
* - true when assignment done without errors or skipped, otherwise false;
* - templated string.
*/
List getTemplatingFromVariables(String assignment, Object envVariables, Map additionalVariablesBinding = [:]) {
Boolean assignmentOk = true
List mentionedVariables = CF.getVariablesMentioningFromString(assignment)
if (!mentionedVariables[0])
return [false, assignmentOk, assignment]
Map bindingVariables = CF.envVarsToMap(envVariables) + additionalVariablesBinding
mentionedVariables.each { mentioned ->
Boolean variableNameIsIncorrect = !checkEnvironmentVariableNameCorrect(mentioned)
Boolean variableIsUndefined = !bindingVariables.containsKey(mentioned)
String errMsg = "The value of this variable will be templated with '' (empty string)."
assignmentOk = errorMsgWrapper(variableNameIsIncorrect, assignmentOk, 3,
String.format("Incorrect variable name '%s' in '%s' value. %s", mentioned, assignment, errMsg))
assignmentOk = errorMsgWrapper(variableIsUndefined, assignmentOk, 3,
String.format("Specified '%s' variable in '%s' value is undefined. %s", mentioned, assignment, errMsg))
bindingVariables[mentioned] = variableNameIsIncorrect || variableIsUndefined ? '' : bindingVariables[mentioned]
}
String assigned = new StreamingTemplateEngine().createTemplate(assignment).make(bindingVariables)
[true, assignmentOk, assigned]
}
/**
* Templating or assign map keys with variable(s).
*
* @param assignMap - map to assign keys in.
* @param assignmentKeysList - list of keys in assignMap needs to be assigned.
* @param envVariables - environment variables for current job build (actually requires a pass of 'env').
* @param allAssignmentsPass - set if you wish to pass previous structure check state.
* @param additionalVariablesBinding - additional (or non-environment) variables for templating.
* @param keysDescription - Keys description for error output.
* @return - arrayList of:
* - true when all keys was assigned without errors or assignment skipped, otherwise false;
* - map with assigned keys.
*/
List templatingMapKeysFromVariables(Map assignMap, List assignmentKeysList, Object envVariables,
Boolean allAssignmentsPass = true, Map additionalVariablesBinding = [:],
String keysDescription = 'Key') {
Boolean allAssignmentsPassNew = allAssignmentsPass
assignmentKeysList.each { currentKey ->
if (assignMap.containsKey(currentKey) && assignMap[currentKey] instanceof String) {
// groovylint-disable-next-line NoDef, VariableTypeRequired
def (Boolean __, Boolean assignOk, String assigned) = getTemplatingFromVariables(assignMap[currentKey]
.toString(), envVariables, additionalVariablesBinding)
allAssignmentsPassNew = errorMsgWrapper(!assignOk, allAssignmentsPass, 3,
String.format("%s '%s' with value '%s' wasn't set properly due to undefined variable(s).",
keysDescription, currentKey, assignMap[currentKey].toString()))
assignMap[currentKey] = assigned
}
}
[allAssignmentsPassNew, assignMap]
}
/**
* Checking that all required pipeline parameters was specified for current build.
*
* @param pipelineSettings - 'universal-wrapper-pipeline-settings' converted to map. See
* https://github.com/alexanderbazhenoff/universal-wrapper-pipeline-settings for details.
* @param pipelineParameters - pipeline parameters for current job build (actually requires a pass of 'params').
* @param envVariables - environment variables for current job build (actually requires a pass of 'env' which is
* class org.jenkinsci.plugins.workflow.cps.EnvActionImpl).
* @return - arrayList of:
* - true when all required variables are specified;
* - changed or unchanged environment variables for current job build.
*/
Boolean checkAllRequiredPipelineParamsAreSet(Map pipelineSettings, Object pipelineParameters, Object envVariables) {
Boolean allSet = true
if (pipelineSettings.get('parameters') && pipelineSettings.get('parameters').get('required')) {
CF.outMsg(1, 'Checking that all required pipeline parameters was defined for current build.')
pipelineSettings.parameters.required.each {
def (String printableParameterName, Boolean parameterIsUndefined) = getPipelineParamNameAndDefinedState(it
as Map, pipelineParameters, envVariables)
if (parameterIsUndefined) {
String assignMessage = ''
def (Boolean paramNeedsToBeAssigned, Boolean assignmentOk, String parameterAssignment, Boolean fail,
Boolean warn) = handleAssignmentWhenPipelineParamIsUnset(it as Map, envVariables)
if (paramNeedsToBeAssigned && assignmentOk && printableParameterName != '<undefined>' &&
parameterAssignment.trim()) {
envVariables[it.name.toString()] = parameterAssignment
} else if (printableParameterName == '<undefined>' || (paramNeedsToBeAssigned && !assignmentOk)) {
assignMessage = assignmentOk ? '' : String.format("(can't be correctly assigned with '%s' %s) ",
it.on_empty.get('assign').toString(), 'variable')
}
allSet = !paramNeedsToBeAssigned && fail ? false : allSet
errorMsgWrapper((warn || (fail && !allSet)), true, fail ? 3 : 2, String.format(
"'%s' pipeline parameter is required, but undefined %s%s. %s", printableParameterName,
assignMessage, 'for current job run', 'Please specify then re-build again.'))
}
}
}
[allSet, envVariables]
}
/**
* Perform regex check and regex replacement of pipeline parameters for current job build.
*
* (Check match when current build pipeline parameter is not empty and a key 'regex' is defined in pipeline settings.
* Also perform regex replacement of parameter value when 'regex_replace' key is defined).
*
* @param allPipelineParams - arrayList of pipeline parameters from settings.
* @param pipelineParameters - pipeline parameters for current job build (actually requires a pass of 'params').
* @param envVariables - environment variables for current job build (actually requires a pass of 'env').
* @param builtinPipelineParameters - additional built-in pipeline parameters arrayList.
* @return - arrayList of:
* - true when all pass;
* - changed or unchanged environment variables for current job build.
*/
Boolean regexCheckAllRequiredPipelineParams(List allPipelineParams, Object pipelineParameters, Object envVariables) {
Boolean allCorrect = true
CF.outMsg(0, 'Starting regex check and regex replacement of pipeline parameters for current build.')
if (allPipelineParams[0]) {
allPipelineParams.each {
def (String printableParamName, Boolean paramIsDefined) = getPipelineParamNameAndDefinedState(it as Map,
pipelineParameters, envVariables, false)
/**
* If regex was set, preform string concatenation for regex list items. Otherwise, regex value is a string
* already.
*/
if (it.get('regex')) {
String regexPattern = ''
if (it.regex instanceof ArrayList && (it.regex as ArrayList)[0]) {
it.regex.each { i -> regexPattern += i.toString() }
} else if (!(it.regex instanceof ArrayList) && it.regex?.trim()) {
regexPattern = it.regex.toString()
}
errorMsgWrapper(regexPattern.trim() as Boolean, true, 0, String.format(
"Found '%s' regex for pipeline parameter '%s'.", regexPattern, printableParamName))
allCorrect = errorMsgWrapper(paramIsDefined && regexPattern.trim() &&
!envVariables[it.name as String].matches(regexPattern), allCorrect, 3,
String.format('%s parameter is incorrect due to regex mismatch.', printableParamName))
}
/**
* Perform regex replacement when regex_replace was set and pipeline parameter is defined for current build.
*/
if (it.get('regex_replace')) {
String msgTemplateNoValue =
"'%s' sub-key value of 'regex_replace' wasn't defined for '%s' pipeline parameter.%s"
String msgTemplateWrongType =
"Wrong type of '%s' value sub-key of 'regex_replace' for '%s' pipeline parameter.%s"
String msgRecommendation = ' Please fix them. Otherwise, replacement will be skipped with an error.'
/** Handle 'to' sub-key of 'regex_replace' parameter item key. */
String regexReplacement = it.regex_replace?.get('to')?.trim() ? it.regex_replace.get('to') : ''
Boolean regexToKeyIsConvertibleToString = detectIsObjectConvertibleToString(it.regex_replace.get('to'))
String pipelineParamKeysWrongTypeMsg = String.format(msgTemplateWrongType, 'to', printableParamName,
msgRecommendation)
Boolean regexReplacementOk = errorMsgWrapper(regexReplacement?.trim() &&
!regexToKeyIsConvertibleToString, false, 3, pipelineParamKeysWrongTypeMsg)
/** Handle 'regex' sub-key of 'regex_replace' parameter item key. */
String regexPattern = it.regex_replace.get('regex')
Boolean regexKeyIsConvertibleToString = detectIsObjectConvertibleToString(it.regex_replace.get('regex'))
if (regexPattern?.length() && regexKeyIsConvertibleToString) {
errorMsgWrapper(!regexReplacement.trim(), false, 0, String.format(msgTemplateNoValue, 'to',
printableParamName, 'Regex match(es) will be removed.'))
if (paramIsDefined && printableParamName != '<undefined>') {
String pipelineParamReplacementMsg = String.format("Replacing '%s' regex to '%s' in '%s' %s...",
regexPattern, regexReplacement, printableParamName, 'pipeline parameter value')
regexReplacementOk = errorMsgWrapper(true, true, 0, pipelineParamReplacementMsg)
envVariables[it.name.toString()] = CF.applyReplaceRegexItems(envVariables[it.name
.toString()] as String, [regexPattern], [regexReplacement])
}
regexReplacementOk = errorMsgWrapper(printableParamName == '<undefined>', regexReplacementOk, 3,
String.format("Replace '%s' regex to '%s' is not possible: 'name' key is %s. %s.",
regexPattern, regexReplacement, 'not defined for pipeline parameter item.',
'Please fix pipeline config. Otherwise, replacement will be skipped with an error'))
} else if (regexPattern?.length() && !regexKeyIsConvertibleToString) {
CF.outMsg(3, String.format(msgTemplateWrongType, 'regex', printableParamName, msgRecommendation))
} else {
CF.outMsg(3, String.format(msgTemplateNoValue, 'regex', printableParamName, msgRecommendation))
}
allCorrect = regexReplacementOk ? allCorrect : false
}
}
}
[allCorrect, envVariables]
}
/**
* Processing wrapper pipeline parameters: check all parameters from pipeline settings are presents. If not inject
* parameters to pipeline.
*
* @param pipelineParams - pipeline parameters in 'universal-wrapper-pipeline-settings' standard and built-in pipeline
* parameters (e.g. 'DEBUG_MODE', etc) converted to arrayList. See: Configuration files format
* description in 'UNIVERSAL WRAPPER PIPELINE SETTINGS' project for details.
* @param currentPipelineParams - pipeline parameters for current job build (actually requires a pass of 'params'). Set
* currentPipelineParams.DRY_RUN to 'true' for dry-run mode.
* @return - arrayList of:
* - true when there is no pipeline parameters in the pipelineSettings;
* - true when pipeline parameters processing pass.
*/
List wrapperPipelineParametersProcessing(List pipelineParams, Object currentPipelineParams) {
def (Boolean noPipelineParams, Boolean allPass) = [true, true]
if (pipelineParams[0]) {
noPipelineParams = false
Boolean updateParamsRequired
CF.outMsg(1, 'Checking that current pipeline parameters are the same with pipeline settings...')
(updateParamsRequired, allPass) = verifyPipelineParamsArePresents(pipelineParams, currentPipelineParams)
if (currentPipelineParams.get('UPDATE_PARAMETERS') || updateParamsRequired) {
CF.outMsg(1, String.format('Current pipeline parameters requires an update from settings. Updating%s',
getBooleanPipelineParamState(currentPipelineParams) ? ' will be skipped in dry-run mode.' : '...'))
updatePipelineParams(pipelineParams, allPass, currentPipelineParams)
}
}
[noPipelineParams, allPass]
}
/**
* Pipeline parameters processing wrapper: git clone, load all pipeline settings, check all current pipeline
* parameters are equal in the settings, check pipeline parameters in the settings are correct, check all pipeline
* parameters was defined properly for current build.
*
* @param settingsGitUrl - repo URL of 'universal-wrapper-pipeline-settings' to load current pipeline settings.
* @param defaultSettingsGitBranch - branch in setting repo.
* @param settingsRelativePathPrefix - prefix for pipeline settings relative path inside the settings project, that will
* be added automatically on yaml load.
* @param pipelineNameRegexReplace - pipeline name regex, a string that will be cut from pipeline name to become a
* filename of yaml pipeline settings to be loaded.
* @param builtinPipelineParameters - Built-in pipeline parameters, which are mandatory and not present in pipeline
* settings.
* @param envVariables - environment variables for current job build (actually requires a pass of 'env').
* @param pipelineParams - jenkins built-in 'params' UnmodifiableMap variable with current build pipeline parameters.
* @return - arrayList of:
* - pipeline failed reason text;
* - true when all pipeline parameters processing pass;
* - true when check pipeline parameters format in the settings pass;
* - map with all pipeline settings loaded from the yaml file in settings repo;
* - environment variables for current job build return.
*/
List pipelineParamsProcessingWrapper(String settingsGitUrl, String defaultSettingsGitBranch,
String settingsRelativePathPrefix, List pipelineNameRegexReplace,
List builtinPipelineParameters, Object envVariables, Object pipelineParams) {
/** Load all pipeline settings then check all current pipeline params are equal to params in pipeline settings. */
String settingsRelativePath = String.format('%s/%s.yaml', settingsRelativePathPrefix,
CF.applyReplaceRegexItems(envVariables.JOB_NAME.toString(), pipelineNameRegexReplace))
Map pipelineSettings = loadPipelineSettings(settingsGitUrl, defaultSettingsGitBranch, settingsRelativePath,
getBooleanPipelineParamState(pipelineParams, 'DEBUG_MODE'))
String pipelineFailReasonText = ''
List allPipelineParams = extractParamsListFromSettingsMap(pipelineSettings, builtinPipelineParameters)
def (Boolean noPipelineParamsInTheConfig, Boolean pipelineParamsProcessingPass) =
wrapperPipelineParametersProcessing(allPipelineParams, pipelineParams)
/** Check pipeline parameters in the settings are correct, all of them was defined properly for current build. */
Boolean checkPipelineParametersPass = true
if (noPipelineParamsInTheConfig && pipelineParamsProcessingPass) {
CF.outMsg(1, 'No pipeline parameters in the config.')
} else if (!noPipelineParamsInTheConfig) {
CF.outMsg(0, 'Checking all pipeline parameters format in the settings.')
checkPipelineParametersPass = checkPipelineParamsFormat(allPipelineParams)
if (checkPipelineParametersPass || getBooleanPipelineParamState(pipelineParams)) {
Boolean requiredPipelineParamsSet
Boolean regexCheckAllRequiredPipelineParamsOk = true
(requiredPipelineParamsSet, env) = checkAllRequiredPipelineParamsAreSet(pipelineSettings, pipelineParams,
envVariables)
if (requiredPipelineParamsSet || getBooleanPipelineParamState(pipelineParams)) {
(regexCheckAllRequiredPipelineParamsOk, env) = regexCheckAllRequiredPipelineParams(allPipelineParams,
pipelineParams, env)
}
pipelineFailReasonText += requiredPipelineParamsSet && regexCheckAllRequiredPipelineParamsOk ? '' :
'Required pipeline parameter(s) was not specified or incorrect. '
}
}
[pipelineFailReasonText, pipelineParamsProcessingPass, checkPipelineParametersPass, pipelineSettings, env]
}
/**
* Check or execute wrapper pipeline from pipeline settings.
*
* @param pipelineSettings - the whole pipeline settings map (pre-converted from yaml) to check and/or execute.
* @param envVariables - environment variables for current job build (actually requires a pass of 'env').
* @param check - true to check pipeline settings structure and parameters.
* @param execute - true to execute pipeline wrapper stages defined in the config, false for dry run. Please note:
* 1. When 'check' is true pipeline settings will be checked, then if 'execute' is true pipeline
* settings will be executed. So you can set both 'check' and 'execute' to true, but it's not
* recommended: use separate function call to check settings first.
* 2. You can also set envVariables.DEBUG_MODE to verbose output and/or envVariables.DRY_RUN to
* perform dry run.
* @return - arrayList of:
* - pipeline stages status map (format described in settings documentation (see:
* Configuration files format description in 'UNIVERSAL WRAPPER PIPELINE SETTINGS' project).;
* - true when checking and execution pass (or skipped), false on checking or execution errors;
* - return of environment variables ('env') that pass to function in 'envVariables'.
*/
List checkOrExecutePipelineWrapperFromSettings(Map pipelineSettings, Object envVariables, Boolean check = false,
Boolean execute = true) {
String currentSubjectMsg = 'in pipeline config'
def (Map universalPipelineWrapperBuiltIns, Boolean executeOk) = [[:], true]
def (String checkTypeMsg, String executeTypeMsg) = [check ? 'check' : '', execute ? 'execute' : '']
String checkExecuteTypeMsg = check && execute ? ' and ' : ''
String functionCallTypes = String.format('%s%s%s', checkTypeMsg, checkExecuteTypeMsg, executeTypeMsg)
Boolean pipelineSettingsContainsStages = pipelineSettings?.get('stages')?.size()
Boolean checkOk = errorMsgWrapper(!pipelineSettingsContainsStages && ((check &&
getBooleanVarStateFromEnv(envVariables)) || execute), true, 0, String.format('No stages %s to %s.',
functionCallTypes, currentSubjectMsg))
/** When pipeline stages are in the config starting iterate of it's items for check and/or execute. */
errorMsgWrapper(pipelineSettingsContainsStages, true, 0, String.format('Starting %s stages %s.', functionCallTypes,
currentSubjectMsg))
for (stageItem in pipelineSettings.stages) {
Boolean stageOk
(universalPipelineWrapperBuiltIns, stageOk, envVariables) = check ? checkOrExecuteStageSettingsItem(
universalPipelineWrapperBuiltIns, stageItem as Map, pipelineSettings, envVariables) :
[universalPipelineWrapperBuiltIns, true, envVariables]
checkOk = stageOk ? checkOk : false
if (execute) {
stage(getPrintableValueKeyFromMapItem(stageItem as Map)) {
(universalPipelineWrapperBuiltIns, stageOk, envVariables) = checkOrExecuteStageSettingsItem(
universalPipelineWrapperBuiltIns, stageItem as Map, pipelineSettings, envVariables, true, false)
executeOk = stageOk ? executeOk : false
}
}
}
[universalPipelineWrapperBuiltIns, checkOk && executeOk, envVariables]
}
/**
* Check or execute all actions in pipeline stage settings item.
*
* (Check actions in the stage, all ansible playbooks, ansible inventories, jobs, scripts or another action according to
* requirements described in: Configuration files format description in 'UNIVERSAL WRAPPER PIPELINE SETTINGS' project).
*
* @param universalPipelineWrapperBuiltIns - pipeline wrapper built-ins variable with report in various formats (see:
* Configuration files format description in 'UNIVERSAL WRAPPER PIPELINE
* SETTINGS' project).
* @param stageItem - stage settings item to check/execute actions in it.
* @param pipelineSettings - the whole pipeline settings map (pre-converted from yaml) to check and/or execute.
* @param envVariables - environment variables for current job build (actually requires a pass of 'env'). Set 'DRY_RUN'
* environment variable (or pipeline parameter) as an element of envVariables to true for dry run
* mode on executing a stage. Set 'DEBUG_MODE' to enable debug mode both for 'check' or 'execute'.
* @param allPass - current overall state of the structure check/execute pass. Will be changed on error(s) or return
* unchanged.
* @param check - set false to execute action item, true to check.
* @return - arrayList of:
* - pipeline wrapper built-ins variable;
* - true when all stage actions execution/checking successfully done;
* - return of environment variables for current job build.
*/
List checkOrExecuteStageSettingsItem(Map universalPipelineWrapperBuiltIns, Map stageItem, Map pipelineSettings,
Object envVariables, Boolean allPass = true, Boolean check = true) {
def (Map actionsRuns, Boolean itsPass) = [[:], allPass]
Map uniPipeWrapBuiltInsInChkExec = universalPipelineWrapperBuiltIns
/** Handling 'name' (with possible assignment), 'actions' and 'parallel' stage keys. */
itsPass = errorMsgWrapper(check && (!stageItem.containsKey('name') ||
!detectIsObjectConvertibleToString(stageItem.get('name'))), itsPass, 3,
"Unable to convert stage name to a string, probably it's undefined or empty.")
String printableStageName = getPrintableValueKeyFromMapItem(stageItem)
Boolean actionsIsNotList = stageItem.containsKey('actions') && !(stageItem.get('actions') instanceof ArrayList)
itsPass = errorMsgWrapper(check && (!stageItem.containsKey('actions') || actionsIsNotList), itsPass, 3,
String.format("Incorrect or undefined actions for '%s' stage.", printableStageName))
itsPass = errorMsgWrapper(check && (stageItem.containsKey('parallel') &&
!detectIsObjectConvertibleToBoolean(stageItem.get('parallel'))), itsPass, 3, String.format(
"Unable to determine 'parallel' value for '%s' stage. Remove them or set as boolean.", printableStageName))
(itsPass, stageItem) = templatingMapKeysFromVariables(stageItem, ['name'], envVariables, itsPass)
List actionsInStage = actionsIsNotList ? [] : stageItem.get('actions') as ArrayList
/** Creating map and processing items from 'actions' key. */
actionsInStage.eachWithIndex { item, Integer index ->
actionsRuns[index] = {
String checkOrExecuteMsg = check ? 'Checking' : 'Executing'
String actionRunsMsg = String.format("action#%s from '%s' stage", index.toString(), printableStageName)
CF.outMsg(check ? 0 : 1, String.format('%s %s', checkOrExecuteMsg, actionRunsMsg))
Boolean checkOrExecuteOk
(uniPipeWrapBuiltInsInChkExec, checkOrExecuteOk, envVariables) = checkOrExecutePipelineActionItem(
uniPipeWrapBuiltInsInChkExec, printableStageName, actionsInStage[index] as Map,
pipelineSettings, index, envVariables, check)
itsPass = checkOrExecuteOk ? itsPass : false
CF.outMsg(0, String.format('%s %s finished. Total:\n%s', checkOrExecuteMsg, actionRunsMsg,
CF.readableMap(uniPipeWrapBuiltInsInChkExec)))
}
}
if (stageItem.get('parallel')?.toBoolean()) {
parallel actionsRuns
} else {
actionsRuns.each {
it.value.call()
}
}
Map multilineStagesReportMap = uniPipeWrapBuiltInsInChkExec?.get('multilineReportStagesMap') ?
uniPipeWrapBuiltInsInChkExec.multilineReportStagesMap as Map : [:]
String stageStatusDetails = stageItem.actions?.size() ? String.format('%s action%s%s.', actionsInStage?.size(),
actionsInStage?.size() > 1 ? 's' : '', stageItem.get('parallel') ? ' in parallel' : '') : '<no actions>'
uniPipeWrapBuiltInsInChkExec.multilineReportStagesMap = CF.addPipelineStepsAndUrls(multilineStagesReportMap,
printableStageName, itsPass, stageStatusDetails, '', false)
uniPipeWrapBuiltInsInChkExec = updateWrapperBuiltInsInStringFormat(uniPipeWrapBuiltInsInChkExec,
'multilineReportStages')
[uniPipeWrapBuiltInsInChkExec, itsPass, envVariables]
}
/**
* Check list of keys from map is probably string or boolean.
*
* @param check - true on check mode, false on execution (to skip messages and no results change).
* @param listOfKeys - list of keys to check from map.
* @param map - map to check from.
* @param isString - check is a string when true, check is a boolean when false.
* @param index - just an index to print 'that map is a part of <index>' for ident.
* @param currentStatus - current status to change on error in check mode, or not to change on execution.
* @return - true when all map items is not empty and correct type.
*/
Boolean checkListOfKeysFromMapProbablyStringOrBoolean(Boolean check, List listOfKeys, Map map, Boolean isString,
String index, Boolean currentStatus = true) {
Boolean currentStatusInCheckListOfMapKeys = currentStatus
listOfKeys.each {
Boolean typeOk = isString ? detectIsObjectConvertibleToString(map.get(it)) :
detectIsObjectConvertibleToBoolean(map.get(it))
if (map.containsKey(it) && !typeOk) {
currentStatusInCheckListOfMapKeys = errorMsgWrapper(check, currentStatusInCheckListOfMapKeys, 3,
String.format("'%s' key in '%s' should be a %s.", it, index, isString ? 'string' : 'boolean'))
} else if (map.containsKey(it) && !map.get(it)?.toString()?.length()) {
currentStatusInCheckListOfMapKeys = errorMsgWrapper(check, currentStatusInCheckListOfMapKeys, 2,
String.format("'%s' key defined for '%s', but it's empty. Remove a key or define it's value.", it,
index))