| Dennis Dmitriev | 5d8a153 | 2019-07-30 16:39:27 +0300 | [diff] [blame] | 1 | package com.mirantis.mk | 
 | 2 |  | 
 | 3 | /** | 
 | 4 |  * | 
 | 5 |  * Run a simple workflow | 
 | 6 |  * | 
 | 7 |  * Function runScenario() executes a sequence of jobs, like | 
 | 8 |  * - Parameters for the jobs are taken from the 'env' object | 
 | 9 |  * - URLs of artifacts from completed jobs may be passed | 
 | 10 |  *   as parameters to the next jobs. | 
 | 11 |  * | 
 | 12 |  * No constants, environment specific logic or other conditional dependencies. | 
 | 13 |  * All the logic should be placed in the workflow jobs, and perform necessary | 
 | 14 |  * actions depending on the job parameters. | 
 | 15 |  * The runScenario() function only provides the | 
 | 16 |  * | 
 | 17 |  */ | 
 | 18 |  | 
| Dennis Dmitriev | 5d8a153 | 2019-07-30 16:39:27 +0300 | [diff] [blame] | 19 | /** | 
| Dennis Dmitriev | 38a45cd | 2023-02-27 14:22:13 +0200 | [diff] [blame] | 20 |  * Print 'global_variables' accumulated during workflow execution, including | 
 | 21 |  * collected artifacts. | 
 | 22 |  * Output is prepared in format that can be copy-pasted into groovy code | 
 | 23 |  * to replay the workflow using the already created artifacts. | 
 | 24 |  * | 
 | 25 |  * @param global_variables  Map that keeps the artifact URLs and used 'env' objects: | 
 | 26 |  *                          {'PARAM1_NAME': <param1 value>, 'PARAM2_NAME': 'http://.../artifacts/param2_value', ...} | 
 | 27 |  */ | 
| Dennis Dmitriev | 44ad94c | 2023-11-29 12:38:12 +0200 | [diff] [blame] | 28 | def printVariables(global_variables, Boolean yamlStyle = true) { | 
 | 29 |     def common = new com.mirantis.mk.Common() | 
 | 30 |     def mcpcommon = new com.mirantis.mcp.Common() | 
 | 31 |     def global_variables_msg = '' | 
 | 32 |     if (yamlStyle) { | 
 | 33 |         global_variables_msg = mcpcommon.dumpYAML(global_variables) | 
 | 34 |     } else { | 
 | 35 |         for (variable in global_variables) { | 
 | 36 |             global_variables_msg += "env.${variable.key}=\"\"\"${variable.value}\"\"\"\n" | 
 | 37 |         } | 
| Dennis Dmitriev | 38a45cd | 2023-02-27 14:22:13 +0200 | [diff] [blame] | 38 |     } | 
| Dennis Dmitriev | 44ad94c | 2023-11-29 12:38:12 +0200 | [diff] [blame] | 39 |     def message = "// Collected global_variables during the workflow:\n${global_variables_msg}" | 
| Dennis Dmitriev | 38a45cd | 2023-02-27 14:22:13 +0200 | [diff] [blame] | 40 |     common.warningMsg(message) | 
 | 41 | } | 
 | 42 |  | 
| Dennis Dmitriev | 44ad94c | 2023-11-29 12:38:12 +0200 | [diff] [blame] | 43 |  | 
 | 44 | /** | 
 | 45 |  * Print stack trace to the console | 
 | 46 |  */ | 
 | 47 | def printStackTrace(e, String prefix = 'at com.mirantis') { | 
 | 48 |     def common = new com.mirantis.mk.Common() | 
 | 49 |     StringWriter writer = new StringWriter() | 
 | 50 |     e.printStackTrace(new PrintWriter(writer)) | 
 | 51 |     String stackTrace = writer | 
 | 52 |  | 
 | 53 |     // Filter the stacktrace to show only the lines related to the specified library | 
 | 54 |     String[] lines = stackTrace.split("\n") | 
 | 55 |     String stackTraceFiltered = '' | 
 | 56 |     Boolean filteredLine = false | 
 | 57 |     for (String line in lines) { | 
 | 58 |         if (line.contains('at ') && line.contains(prefix)) { | 
 | 59 |             if (!filteredLine) { | 
 | 60 |                 stackTraceFiltered += "...\n" | 
 | 61 |                 filteredLine = true | 
 | 62 |             } | 
 | 63 |             stackTraceFiltered += "${line}\n" | 
 | 64 |         } | 
 | 65 |         else if (!line.contains('at ')) { | 
 | 66 |             if (filteredLine) { | 
 | 67 |                 stackTraceFiltered += "...\n" | 
 | 68 |                 filteredLine = false | 
 | 69 |             } | 
 | 70 |             stackTraceFiltered += "${line}\n" | 
 | 71 |         } | 
 | 72 |     } | 
 | 73 |     common.errorMsg("Stack trace:\n${stackTraceFiltered}") | 
 | 74 | } | 
 | 75 |  | 
 | 76 |  | 
| Dennis Dmitriev | 38a45cd | 2023-02-27 14:22:13 +0200 | [diff] [blame] | 77 | /** | 
| Dennis Dmitriev | 5f014d8 | 2020-04-29 00:00:34 +0300 | [diff] [blame] | 78 |  * Get Jenkins parameter names, values and types from jobName | 
 | 79 |  * @param jobName job name | 
 | 80 |  * @return Map with parameter names as keys and the following map as values: | 
 | 81 |  *  [ | 
 | 82 |  *    <str name1>: [type: <str cls1>, use_variable: <str name1>, defaultValue: <cls value1>], | 
 | 83 |  *    <str name2>: [type: <str cls2>, use_variable: <str name2>, defaultValue: <cls value2>], | 
 | 84 |  *  ] | 
 | 85 |  */ | 
 | 86 | def getJobDefaultParameters(jobName) { | 
 | 87 |     def jenkinsUtils = new com.mirantis.mk.JenkinsUtils() | 
| Dennis Dmitriev | 44ad94c | 2023-11-29 12:38:12 +0200 | [diff] [blame] | 88 |     def item = jenkinsUtils.getJobByName(jobName) | 
| Dennis Dmitriev | 5f014d8 | 2020-04-29 00:00:34 +0300 | [diff] [blame] | 89 |     def parameters = [:] | 
| Dennis Dmitriev | 44ad94c | 2023-11-29 12:38:12 +0200 | [diff] [blame] | 90 |     // def prop = item.getProperty(ParametersDefinitionProperty.class) | 
 | 91 |     def prop = item.getProperty(ParametersDefinitionProperty) | 
| azvyagintsev | 75390d9 | 2021-04-12 14:20:11 +0300 | [diff] [blame] | 92 |     if (prop != null) { | 
 | 93 |         for (param in prop.getParameterDefinitions()) { | 
| Dennis Dmitriev | 5f014d8 | 2020-04-29 00:00:34 +0300 | [diff] [blame] | 94 |             def defaultParam = param.getDefaultParameterValue() | 
 | 95 |             def cls = defaultParam.getClass().getName() | 
 | 96 |             def value = defaultParam.getValue() | 
 | 97 |             def name = defaultParam.getName() | 
 | 98 |             parameters[name] = [type: cls, use_variable: name, defaultValue: value] | 
 | 99 |         } | 
 | 100 |     } | 
 | 101 |     return parameters | 
 | 102 | } | 
 | 103 |  | 
| Dennis Dmitriev | 44ad94c | 2023-11-29 12:38:12 +0200 | [diff] [blame] | 104 |  | 
| Dennis Dmitriev | 5f014d8 | 2020-04-29 00:00:34 +0300 | [diff] [blame] | 105 | /** | 
| Dennis Dmitriev | 44ad94c | 2023-11-29 12:38:12 +0200 | [diff] [blame] | 106 |  * Generate parameters for a Jenkins job using different sources | 
| Dennis Dmitriev | 5d8a153 | 2019-07-30 16:39:27 +0300 | [diff] [blame] | 107 |  * | 
| Dennis Dmitriev | 5d8a153 | 2019-07-30 16:39:27 +0300 | [diff] [blame] | 108 |  * @param job_parameters    Map that declares which values from global_variables should be used, in the following format: | 
 | 109 |  *                          {'PARAM_NAME': {'type': <job parameter $class name>, 'use_variable': <a key from global_variables>}, ...} | 
| Dennis Dmitriev | ce47093 | 2019-09-18 18:31:11 +0300 | [diff] [blame] | 110 |  *                          or | 
| Dennis Dmitriev | cae9bca | 2019-09-19 16:10:03 +0300 | [diff] [blame] | 111 |  *                          {'PARAM_NAME': {'type': <job parameter $class name>, 'get_variable_from_url': <a key from global_variables which contains URL with required content>}, ...} | 
 | 112 |  *                          or | 
| Dennis Dmitriev | ce47093 | 2019-09-18 18:31:11 +0300 | [diff] [blame] | 113 |  *                          {'PARAM_NAME': {'type': <job parameter $class name>, 'use_template': <a GString multiline template with variables from global_variables>}, ...} | 
| Dennis Dmitriev | 6c355be | 2021-11-09 14:06:56 +0200 | [diff] [blame] | 114 |  *                          or | 
 | 115 |  *                          {'PARAM_NAME': {'type': <job parameter $class name>, 'get_variable_from_yaml': {'yaml_url': <URL with YAML content>, | 
 | 116 |  *                                                                                                          'yaml_key': <a groovy-interpolating path to the key in the YAML, starting from dot '.'> } }, ...} | 
| Dennis Dmitriev | 44ad94c | 2023-11-29 12:38:12 +0200 | [diff] [blame] | 117 |  *                          or | 
 | 118 |  *                          {'PARAM_NAME': {'type': <job parameter $class name>, 'use_variables_map': <a nested map of job_parameters>}, ...} | 
 | 119 |  *                                         , where job_parameters may contain a special 'type': '_defaultText' for a Yaml with some additional parameters for this map | 
| Dennis Dmitriev | 5d8a153 | 2019-07-30 16:39:27 +0300 | [diff] [blame] | 120 |  * @param global_variables  Map that keeps the artifact URLs and used 'env' objects: | 
 | 121 |  *                          {'PARAM1_NAME': <param1 value>, 'PARAM2_NAME': 'http://.../artifacts/param2_value', ...} | 
 | 122 |  */ | 
| Dennis Dmitriev | 44ad94c | 2023-11-29 12:38:12 +0200 | [diff] [blame] | 123 | def generateParameters(job_parameters, global_variables) { | 
| Dennis Dmitriev | 5d8a153 | 2019-07-30 16:39:27 +0300 | [diff] [blame] | 124 |     def parameters = [] | 
| azvyagintsev | 0d97815 | 2022-01-27 14:01:33 +0200 | [diff] [blame] | 125 |     def common = new com.mirantis.mk.Common() | 
| Dennis Dmitriev | 44ad94c | 2023-11-29 12:38:12 +0200 | [diff] [blame] | 126 |     def mcpcommon = new com.mirantis.mcp.Common() | 
| Dennis Dmitriev | cae9bca | 2019-09-19 16:10:03 +0300 | [diff] [blame] | 127 |     def http = new com.mirantis.mk.Http() | 
| Dennis Dmitriev | ce47093 | 2019-09-18 18:31:11 +0300 | [diff] [blame] | 128 |     def engine = new groovy.text.GStringTemplateEngine() | 
 | 129 |     def template | 
| Dennis Dmitriev | 6c355be | 2021-11-09 14:06:56 +0200 | [diff] [blame] | 130 |     def yamls_from_urls = [:] | 
| Dennis Dmitriev | cae9bca | 2019-09-19 16:10:03 +0300 | [diff] [blame] | 131 |     def base = [:] | 
 | 132 |     base["url"] = '' | 
 | 133 |     def variable_content | 
| Dennis Dmitriev | 44ad94c | 2023-11-29 12:38:12 +0200 | [diff] [blame] | 134 |     def env_variables = common.getEnvAsMap() | 
| Dennis Dmitriev | 5d8a153 | 2019-07-30 16:39:27 +0300 | [diff] [blame] | 135 |  | 
 | 136 |     // Collect required parameters from 'global_variables' or 'env' | 
| azvyagintsev | 2501527 | 2023-11-28 17:31:18 +0200 | [diff] [blame] | 137 |     def _msg = '' | 
| Dennis Dmitriev | 5d8a153 | 2019-07-30 16:39:27 +0300 | [diff] [blame] | 138 |     for (param in job_parameters) { | 
| Dennis Dmitriev | ce47093 | 2019-09-18 18:31:11 +0300 | [diff] [blame] | 139 |         if (param.value.containsKey('use_variable')) { | 
 | 140 |             if (!global_variables[param.value.use_variable]) { | 
 | 141 |                 global_variables[param.value.use_variable] = env[param.value.use_variable] ?: '' | 
 | 142 |             } | 
 | 143 |             parameters.add([$class: "${param.value.type}", name: "${param.key}", value: global_variables[param.value.use_variable]]) | 
| azvyagintsev | 2501527 | 2023-11-28 17:31:18 +0200 | [diff] [blame] | 144 |             _msg += "\n${param.key}: <${param.value.type}> From:${param.value.use_variable}, Value:${global_variables[param.value.use_variable]}" | 
| Dennis Dmitriev | cae9bca | 2019-09-19 16:10:03 +0300 | [diff] [blame] | 145 |         } else if (param.value.containsKey('get_variable_from_url')) { | 
 | 146 |             if (!global_variables[param.value.get_variable_from_url]) { | 
 | 147 |                 global_variables[param.value.get_variable_from_url] = env[param.value.get_variable_from_url] ?: '' | 
 | 148 |             } | 
| Andrew Baraniuk | e0aef1e | 2019-10-16 14:50:10 +0300 | [diff] [blame] | 149 |             if (global_variables[param.value.get_variable_from_url]) { | 
| Dennis Dmitriev | 44ad94c | 2023-11-29 12:38:12 +0200 | [diff] [blame] | 150 |                 variable_content = http.restGet(base, global_variables[param.value.get_variable_from_url]) | 
 | 151 |                 // http.restGet() attempts to read the response as a JSON, and may return an object instead of a string | 
 | 152 |                 variable_content = "${variable_content}".trim() | 
| Andrew Baraniuk | e0aef1e | 2019-10-16 14:50:10 +0300 | [diff] [blame] | 153 |                 parameters.add([$class: "${param.value.type}", name: "${param.key}", value: variable_content]) | 
| azvyagintsev | 2501527 | 2023-11-28 17:31:18 +0200 | [diff] [blame] | 154 |                 _msg += "\n${param.key}: <${param.value.type}> Content from url: ${variable_content}" | 
| Andrew Baraniuk | e0aef1e | 2019-10-16 14:50:10 +0300 | [diff] [blame] | 155 |             } else { | 
| azvyagintsev | 2501527 | 2023-11-28 17:31:18 +0200 | [diff] [blame] | 156 |                 _msg += "\n${param.key} is empty, skipping get_variable_from_url" | 
| Andrew Baraniuk | e0aef1e | 2019-10-16 14:50:10 +0300 | [diff] [blame] | 157 |             } | 
| Dennis Dmitriev | 6c355be | 2021-11-09 14:06:56 +0200 | [diff] [blame] | 158 |         } else if (param.value.containsKey('get_variable_from_yaml')) { | 
 | 159 |             if (param.value.get_variable_from_yaml.containsKey('yaml_url') && param.value.get_variable_from_yaml.containsKey('yaml_key')) { | 
 | 160 |                 // YAML url is stored in an environment or a global variable (like 'SI_CONFIG_ARTIFACT') | 
| azvyagintsev | 0d97815 | 2022-01-27 14:01:33 +0200 | [diff] [blame] | 161 |                 def yaml_url_var = param.value.get_variable_from_yaml.yaml_url | 
| Dennis Dmitriev | 6c355be | 2021-11-09 14:06:56 +0200 | [diff] [blame] | 162 |                 if (!global_variables[yaml_url_var]) { | 
 | 163 |                     global_variables[yaml_url_var] = env[yaml_url_var] ?: '' | 
 | 164 |                 } | 
| Dennis Dmitriev | 44ad94c | 2023-11-29 12:38:12 +0200 | [diff] [blame] | 165 |                 def yaml_url = global_variables[yaml_url_var]  // Real YAML URL | 
 | 166 |                 def yaml_key = param.value.get_variable_from_yaml.yaml_key | 
| azvyagintsev | 353b876 | 2022-01-14 12:30:43 +0200 | [diff] [blame] | 167 |                 // Key to get the data from YAML, to interpolate in the groovy, for example: | 
 | 168 |                 //  <yaml_map_variable>.key.to.the[0].required.data , where yaml_key = '.key.to.the[0].required.data' | 
| Dennis Dmitriev | 6c355be | 2021-11-09 14:06:56 +0200 | [diff] [blame] | 169 |                 if (yaml_url) { | 
 | 170 |                     if (!yamls_from_urls[yaml_url]) { | 
| azvyagintsev | 2501527 | 2023-11-28 17:31:18 +0200 | [diff] [blame] | 171 |                         _msg += "\nReading YAML from ${yaml_url} for ${param.key}" | 
| Dennis Dmitriev | 44ad94c | 2023-11-29 12:38:12 +0200 | [diff] [blame] | 172 |                         def yaml_content = http.restGet(base, yaml_url) | 
| Dennis Dmitriev | 6c355be | 2021-11-09 14:06:56 +0200 | [diff] [blame] | 173 |                         yamls_from_urls[yaml_url] = readYaml text: yaml_content | 
 | 174 |                     } | 
| azvyagintsev | 2501527 | 2023-11-28 17:31:18 +0200 | [diff] [blame] | 175 |                     _msg += "\nGetting key ${yaml_key} from YAML ${yaml_url} for ${param.key}" | 
| Dennis Dmitriev | 44ad94c | 2023-11-29 12:38:12 +0200 | [diff] [blame] | 176 |                     def template_variables = [ | 
| azvyagintsev | 2501527 | 2023-11-28 17:31:18 +0200 | [diff] [blame] | 177 |                       'yaml_data': yamls_from_urls[yaml_url], | 
| Dennis Dmitriev | 6c355be | 2021-11-09 14:06:56 +0200 | [diff] [blame] | 178 |                     ] | 
| Dennis Dmitriev | 44ad94c | 2023-11-29 12:38:12 +0200 | [diff] [blame] | 179 |                     def request = "\${yaml_data${yaml_key}}" | 
| Dennis Dmitriev | 5e07671 | 2022-02-08 15:05:21 +0200 | [diff] [blame] | 180 |                     def result | 
| Dennis Dmitriev | 450cf73 | 2021-11-11 14:59:17 +0200 | [diff] [blame] | 181 |                     // Catch errors related to wrong key or index in the list or map objects | 
 | 182 |                     // For wrong key in map or wrong index in list, groovy returns <null> object, | 
 | 183 |                     // but it can be catched only after the string interpolation <template.toString()>, | 
 | 184 |                     // so we should catch the string 'null' instead of object <null>. | 
 | 185 |                     try { | 
 | 186 |                         template = engine.createTemplate(request).make(template_variables) | 
| Dennis Dmitriev | 5e07671 | 2022-02-08 15:05:21 +0200 | [diff] [blame] | 187 |                         result = template.toString() | 
| Dennis Dmitriev | 450cf73 | 2021-11-11 14:59:17 +0200 | [diff] [blame] | 188 |                         if (result == 'null') { | 
 | 189 |                             error "No such key or index, got 'null'" | 
 | 190 |                         } | 
 | 191 |                     } catch (e) { | 
 | 192 |                         error("Failed to get the key ${yaml_key} from YAML ${yaml_url}: " + e.toString()) | 
 | 193 |                     } | 
 | 194 |  | 
 | 195 |                     parameters.add([$class: "${param.value.type}", name: "${param.key}", value: result]) | 
| azvyagintsev | 2501527 | 2023-11-28 17:31:18 +0200 | [diff] [blame] | 196 |                     _msg += "\n${param.key}: <${param.value.type}>\n${result}" | 
| Dennis Dmitriev | 6c355be | 2021-11-09 14:06:56 +0200 | [diff] [blame] | 197 |                 } else { | 
| azvyagintsev | 353b876 | 2022-01-14 12:30:43 +0200 | [diff] [blame] | 198 |                     common.warningMsg("'yaml_url' in ${param.key} is empty, skipping get_variable_from_yaml") | 
| Dennis Dmitriev | 6c355be | 2021-11-09 14:06:56 +0200 | [diff] [blame] | 199 |                 } | 
 | 200 |             } else { | 
| azvyagintsev | 353b876 | 2022-01-14 12:30:43 +0200 | [diff] [blame] | 201 |                 common.warningMsg("${param.key} missing 'yaml_url'/'yaml_key' parameters, skipping get_variable_from_yaml") | 
| Dennis Dmitriev | 6c355be | 2021-11-09 14:06:56 +0200 | [diff] [blame] | 202 |             } | 
| Dennis Dmitriev | ce47093 | 2019-09-18 18:31:11 +0300 | [diff] [blame] | 203 |         } else if (param.value.containsKey('use_template')) { | 
| Dennis Dmitriev | 44ad94c | 2023-11-29 12:38:12 +0200 | [diff] [blame] | 204 |             template = engine.createTemplate(param.value.use_template.toString()).make(env_variables + global_variables) | 
| Dennis Dmitriev | ce47093 | 2019-09-18 18:31:11 +0300 | [diff] [blame] | 205 |             parameters.add([$class: "${param.value.type}", name: "${param.key}", value: template.toString()]) | 
| azvyagintsev | 2501527 | 2023-11-28 17:31:18 +0200 | [diff] [blame] | 206 |             _msg += "\n${param.key}: <${param.value.type}>\n${template.toString()}" | 
| Dennis Dmitriev | 44ad94c | 2023-11-29 12:38:12 +0200 | [diff] [blame] | 207 |         } else if (param.value.containsKey('use_variables_map')) { | 
| azvyagintsev | 2501527 | 2023-11-28 17:31:18 +0200 | [diff] [blame] | 208 |             // Generate multistring YAML with key/value pairs (like job_parameters) from a nested parameters map | 
 | 209 |             def nested_parameters = generateParameters(param.value.use_variables_map, global_variables) | 
 | 210 |             def nested_values = [:] | 
 | 211 |             for (_parameter in nested_parameters) { | 
 | 212 |                 if (_parameter.$class == '_defaultText') { | 
 | 213 |                     // This is a special type for multiline with default values | 
 | 214 |                     def _values = readYaml(text: _parameter.value ?: '---') ?: [:] | 
 | 215 |                     _values << nested_values | 
 | 216 |                     nested_values = _values | 
 | 217 |                 } else { | 
 | 218 |                     nested_values[_parameter.name] = _parameter.value | 
 | 219 |                 } | 
 | 220 |             } | 
 | 221 |             def multistring_value = mcpcommon.dumpYAML(nested_values) | 
 | 222 |             parameters.add([$class: "${param.value.type}", name: "${param.key}", value: multistring_value]) | 
 | 223 |             _msg += "\n${param.key}: <${param.value.type}>\n${multistring_value}" | 
| Dennis Dmitriev | 5d8a153 | 2019-07-30 16:39:27 +0300 | [diff] [blame] | 224 |         } | 
| Dennis Dmitriev | 5d8a153 | 2019-07-30 16:39:27 +0300 | [diff] [blame] | 225 |     } | 
| azvyagintsev | 2501527 | 2023-11-28 17:31:18 +0200 | [diff] [blame] | 226 |     common.infoMsg(_msg) | 
| Dennis Dmitriev | 44ad94c | 2023-11-29 12:38:12 +0200 | [diff] [blame] | 227 |     return parameters | 
 | 228 | } | 
| Dennis Dmitriev | 5d8a153 | 2019-07-30 16:39:27 +0300 | [diff] [blame] | 229 |  | 
| Dennis Dmitriev | 44ad94c | 2023-11-29 12:38:12 +0200 | [diff] [blame] | 230 |  | 
 | 231 | /** | 
 | 232 |  * Run a Jenkins job using the collected parameters | 
 | 233 |  * | 
 | 234 |  * @param job_name          Name of the running job | 
 | 235 |  * @param job_parameters    Map that declares which values from global_variables should be used | 
 | 236 |  * @param global_variables  Map that keeps the artifact URLs and used 'env' objects | 
 | 237 |  * @param propagate         Boolean. If false: allows to collect artifacts after job is finished, even with FAILURE status | 
 | 238 |  *                          If true: immediatelly fails the pipeline. DO NOT USE 'true' if you want to collect artifacts | 
 | 239 |  *                          for 'finally' steps | 
 | 240 |  */ | 
 | 241 | def runJob(job_name, job_parameters, global_variables, Boolean propagate = false) { | 
 | 242 |  | 
 | 243 |     def parameters = generateParameters(job_parameters, global_variables) | 
| Dennis Dmitriev | 5d8a153 | 2019-07-30 16:39:27 +0300 | [diff] [blame] | 244 |     // Build the job | 
| Dennis Dmitriev | e09e029 | 2019-07-30 16:39:27 +0300 | [diff] [blame] | 245 |     def job_info = build job: "${job_name}", parameters: parameters, propagate: propagate | 
| Dennis Dmitriev | 5d8a153 | 2019-07-30 16:39:27 +0300 | [diff] [blame] | 246 |     return job_info | 
 | 247 | } | 
 | 248 |  | 
| azvyagintsev | 061179d | 2021-05-05 16:52:18 +0300 | [diff] [blame] | 249 | def runOrGetJob(job_name, job_parameters, global_variables, propagate, String fullTaskName = '') { | 
 | 250 |     /** | 
 | 251 |      *  Run job directly or try to find already executed build | 
 | 252 |      *  Flow, in case CI_JOBS_OVERRIDES passed: | 
 | 253 |      * | 
 | 254 |      * | 
 | 255 |      *  CI_JOBS_OVERRIDES = text in yaml|json format | 
 | 256 |      *  CI_JOBS_OVERRIDES = 'kaas-testing-core-release-artifact'                : 3505 | 
 | 257 |      *                     'reindex-testing-core-release-index-with-rc'        : 2822 | 
 | 258 |      *                     'si-test-release-sanity-check-prepare-configuration': 1877 | 
 | 259 |      */ | 
| Dennis Dmitriev | 44ad94c | 2023-11-29 12:38:12 +0200 | [diff] [blame] | 260 |     def common = new com.mirantis.mk.Common() | 
| azvyagintsev | 061179d | 2021-05-05 16:52:18 +0300 | [diff] [blame] | 261 |     def jobsOverrides = readYaml(text: env.CI_JOBS_OVERRIDES ?: '---') ?: [:] | 
 | 262 |     // get id of overriding job | 
 | 263 |     def jobOverrideID = jobsOverrides.getOrDefault(fullTaskName, '') | 
| azvyagintsev | 061179d | 2021-05-05 16:52:18 +0300 | [diff] [blame] | 264 |     if (fullTaskName in jobsOverrides.keySet()) { | 
 | 265 |         common.warningMsg("Overriding: ${fullTaskName}/${job_name} <<< ${jobOverrideID}") | 
 | 266 |         common.infoMsg("For debug pin use:\n'${fullTaskName}' : ${jobOverrideID}") | 
| Dennis Dmitriev | 44ad94c | 2023-11-29 12:38:12 +0200 | [diff] [blame] | 267 |         return Jenkins.instance.getItemByFullName(job_name, hudson.model.Job).getBuildByNumber(jobOverrideID.toInteger()) | 
| azvyagintsev | 061179d | 2021-05-05 16:52:18 +0300 | [diff] [blame] | 268 |     } else { | 
 | 269 |         return runJob(job_name, job_parameters, global_variables, propagate) | 
 | 270 |     } | 
 | 271 | } | 
 | 272 |  | 
| Dennis Dmitriev | 5d8a153 | 2019-07-30 16:39:27 +0300 | [diff] [blame] | 273 | /** | 
 | 274 |  * Store URLs of the specified artifacts to the global_variables | 
 | 275 |  * | 
| Andrii Baraniuk | cf4c2fa | 2023-04-18 13:53:32 +0300 | [diff] [blame] | 276 |  * @param build_url           URL of the completed job | 
 | 277 |  * @param step_artifacts      Map that contains artifact names in the job, and variable names | 
 | 278 |  *                            where the URLs to that atrifacts should be stored, for example: | 
 | 279 |  *                            {'ARTIFACT1': 'logs.tar.gz', 'ARTIFACT2': 'test_report.xml', ...} | 
 | 280 |  * @param global_variables    Map that will keep the artifact URLs. Variable 'ARTIFACT1', for example, | 
 | 281 |  *                            be used in next job parameters: {'ARTIFACT1_URL':{ 'use_variable': 'ARTIFACT1', ...}} | 
| Dennis Dmitriev | 5d8a153 | 2019-07-30 16:39:27 +0300 | [diff] [blame] | 282 |  * | 
| Andrii Baraniuk | cf4c2fa | 2023-04-18 13:53:32 +0300 | [diff] [blame] | 283 |  *                            If the artifact with the specified name not found, the parameter ARTIFACT1_URL | 
 | 284 |  *                            will be empty. | 
 | 285 |  * @param artifactory_server  Artifactory server ID defined in Jenkins config | 
| Dennis Dmitriev | 5d8a153 | 2019-07-30 16:39:27 +0300 | [diff] [blame] | 286 |  * | 
 | 287 |  */ | 
| Dennis Dmitriev | aa0fa74 | 2024-03-27 22:38:25 +0200 | [diff] [blame] | 288 | def storeArtifacts(build_url, step_artifacts, global_variables, job_name, build_num, artifactory_url = '', artifactory_server = '', artifacts_msg='local artifacts') { | 
| Dmitry Tyzhnenko | f446e41 | 2020-04-06 13:24:54 +0300 | [diff] [blame] | 289 |     def common = new com.mirantis.mk.Common() | 
| Dennis Dmitriev | 5d8a153 | 2019-07-30 16:39:27 +0300 | [diff] [blame] | 290 |     def http = new com.mirantis.mk.Http() | 
| Andrii Baraniuk | cf4c2fa | 2023-04-18 13:53:32 +0300 | [diff] [blame] | 291 |     def artifactory = new com.mirantis.mcp.MCPArtifactory() | 
| Dennis Dmitriev | 44ad94c | 2023-11-29 12:38:12 +0200 | [diff] [blame] | 292 |     if (!artifactory_url && !artifactory_server) { | 
| Andrii Baraniuk | cf4c2fa | 2023-04-18 13:53:32 +0300 | [diff] [blame] | 293 |         artifactory_url = 'https://artifactory.mcp.mirantis.net/artifactory/api/storage/si-local/jenkins-job-artifacts' | 
 | 294 |     } else if (!artifactory_url && artifactory_server) { | 
 | 295 |         artifactory_url = artifactory.getArtifactoryServer(artifactory_server).getUrl() + '/artifactory/api/storage/si-local/jenkins-job-artifacts' | 
| Aleksey Zvyagintsev | 25ed4a5 | 2021-05-12 14:35:03 +0000 | [diff] [blame] | 296 |     } | 
| Andrii Baraniuk | cf4c2fa | 2023-04-18 13:53:32 +0300 | [diff] [blame] | 297 |  | 
| Dmitry Tyzhnenko | f446e41 | 2020-04-06 13:24:54 +0300 | [diff] [blame] | 298 |     def baseJenkins = [:] | 
 | 299 |     def baseArtifactory = [:] | 
 | 300 |     build_url = build_url.replaceAll(~/\/+$/, "") | 
| Dmitry Tyzhnenko | f446e41 | 2020-04-06 13:24:54 +0300 | [diff] [blame] | 301 |     baseArtifactory["url"] = artifactory_url + "/${job_name}/${build_num}" | 
| Dmitry Tyzhnenko | f446e41 | 2020-04-06 13:24:54 +0300 | [diff] [blame] | 302 |     baseJenkins["url"] = build_url | 
 | 303 |     def job_config = http.restGet(baseJenkins, "/api/json/") | 
| Dennis Dmitriev | 5d8a153 | 2019-07-30 16:39:27 +0300 | [diff] [blame] | 304 |     def job_artifacts = job_config['artifacts'] | 
| Dennis Dmitriev | aa0fa74 | 2024-03-27 22:38:25 +0200 | [diff] [blame] | 305 |     common.infoMsg("Attempt to store ${artifacts_msg} for: ${job_name}/${build_num}") | 
| Dennis Dmitriev | 5d8a153 | 2019-07-30 16:39:27 +0300 | [diff] [blame] | 306 |     for (artifact in step_artifacts) { | 
| Dmitry Tyzhnenko | f446e41 | 2020-04-06 13:24:54 +0300 | [diff] [blame] | 307 |         try { | 
| azvyagintsev | 0d97815 | 2022-01-27 14:01:33 +0200 | [diff] [blame] | 308 |             def artifactoryResp = http.restGet(baseArtifactory, "/${artifact.value}") | 
| Dmitry Tyzhnenko | f446e41 | 2020-04-06 13:24:54 +0300 | [diff] [blame] | 309 |             global_variables[artifact.key] = artifactoryResp.downloadUri | 
| azvyagintsev | 0d97815 | 2022-01-27 14:01:33 +0200 | [diff] [blame] | 310 |             common.infoMsg("Artifact URL ${artifactoryResp.downloadUri} stored to ${artifact.key}") | 
| Dmitry Tyzhnenko | f446e41 | 2020-04-06 13:24:54 +0300 | [diff] [blame] | 311 |             continue | 
 | 312 |         } catch (Exception e) { | 
| azvyagintsev | 0d97815 | 2022-01-27 14:01:33 +0200 | [diff] [blame] | 313 |             common.warningMsg("Can't find an artifact in ${artifactory_url}/${job_name}/${build_num}/${artifact.value} to store in ${artifact.key}\n" + | 
 | 314 |               "error code ${e.message}") | 
| Dmitry Tyzhnenko | f446e41 | 2020-04-06 13:24:54 +0300 | [diff] [blame] | 315 |         } | 
 | 316 |  | 
| azvyagintsev | 0d97815 | 2022-01-27 14:01:33 +0200 | [diff] [blame] | 317 |         def job_artifact = job_artifacts.findAll { item -> artifact.value == item['fileName'] || artifact.value == item['relativePath'] } | 
| Dennis Dmitriev | 5d8a153 | 2019-07-30 16:39:27 +0300 | [diff] [blame] | 318 |         if (job_artifact.size() == 1) { | 
 | 319 |             // Store artifact URL | 
| Dmitry Tyzhnenko | f446e41 | 2020-04-06 13:24:54 +0300 | [diff] [blame] | 320 |             def artifact_url = "${build_url}/artifact/${job_artifact[0]['relativePath']}" | 
| Dennis Dmitriev | 5d8a153 | 2019-07-30 16:39:27 +0300 | [diff] [blame] | 321 |             global_variables[artifact.key] = artifact_url | 
| azvyagintsev | 0d97815 | 2022-01-27 14:01:33 +0200 | [diff] [blame] | 322 |             common.infoMsg("Artifact URL ${artifact_url} stored to ${artifact.key}") | 
| Dennis Dmitriev | 5d8a153 | 2019-07-30 16:39:27 +0300 | [diff] [blame] | 323 |         } else if (job_artifact.size() > 1) { | 
 | 324 |             // Error: too many artifacts with the same name, fail the job | 
 | 325 |             error "Multiple artifacts ${artifact.value} for ${artifact.key} found in the build results ${build_url}, expected one:\n${job_artifact}" | 
 | 326 |         } else { | 
 | 327 |             // Warning: no artifact with expected name | 
| azvyagintsev | 0d97815 | 2022-01-27 14:01:33 +0200 | [diff] [blame] | 328 |             common.warningMsg("Artifact ${artifact.value} for ${artifact.key} not found in the build results ${build_url} and in the artifactory ${artifactory_url}/${job_name}/${build_num}/, found the following artifacts in Jenkins:\n${job_artifacts}") | 
| Andrew Baraniuk | e0aef1e | 2019-10-16 14:50:10 +0300 | [diff] [blame] | 329 |             global_variables[artifact.key] = '' | 
| Dennis Dmitriev | 5d8a153 | 2019-07-30 16:39:27 +0300 | [diff] [blame] | 330 |         } | 
 | 331 |     } | 
 | 332 | } | 
 | 333 |  | 
| Dennis Dmitriev | 44ad94c | 2023-11-29 12:38:12 +0200 | [diff] [blame] | 334 |  | 
 | 335 | def getStatusStyle(status) { | 
 | 336 |     // Styling the status of job result | 
 | 337 |     def status_style = '' | 
 | 338 |     switch (status) { | 
 | 339 |         case "SUCCESS": | 
 | 340 |             status_style = "<td style='color: green;'><img src='/images/16x16/blue.png' alt='SUCCESS'>" | 
 | 341 |             break | 
 | 342 |         case "UNSTABLE": | 
 | 343 |             status_style = "<td style='color: #FF5733;'><img src='/images/16x16/yellow.png' alt='UNSTABLE'>" | 
 | 344 |             break | 
 | 345 |         case "ABORTED": | 
 | 346 |             status_style = "<td style='color: red;'><img src='/images/16x16/aborted.png' alt='ABORTED'>" | 
 | 347 |             break | 
 | 348 |         case "NOT_BUILT": | 
 | 349 |             status_style = "<td style='color: red;'><img src='/images/16x16/aborted.png' alt='NOT_BUILT'>" | 
 | 350 |             break | 
 | 351 |         case "FAILURE": | 
 | 352 |             status_style = "<td style='color: red;'><img src='/images/16x16/red.png' alt='FAILURE'>" | 
 | 353 |             break | 
 | 354 |         default: | 
 | 355 |             status_style = "<td>-" | 
 | 356 |     } | 
 | 357 |     return status_style | 
 | 358 | } | 
 | 359 |  | 
 | 360 |  | 
 | 361 | def getTrStyle(jobdata) { | 
 | 362 |     def trstyle = "<tr>" | 
 | 363 |     // Grey background for 'finally' jobs in list | 
 | 364 |     if (jobdata.getOrDefault('type', '') == 'finally') { | 
 | 365 |         trstyle = "<tr style='background: #DDDDDD;'>" | 
 | 366 |     } | 
 | 367 |     return trstyle | 
 | 368 | } | 
 | 369 |  | 
 | 370 |  | 
| AndrewB | 8505a7f | 2020-06-05 13:42:08 +0300 | [diff] [blame] | 371 | /** | 
| Dennis Dmitriev | 44ad94c | 2023-11-29 12:38:12 +0200 | [diff] [blame] | 372 |  * Update a 'job' step description | 
| AndrewB | 8505a7f | 2020-06-05 13:42:08 +0300 | [diff] [blame] | 373 |  * | 
| Dennis Dmitriev | 44ad94c | 2023-11-29 12:38:12 +0200 | [diff] [blame] | 374 |  * @param jobsdata               Map with a 'job' step details and status | 
| AndrewB | 8505a7f | 2020-06-05 13:42:08 +0300 | [diff] [blame] | 375 |  */ | 
| Dennis Dmitriev | 44ad94c | 2023-11-29 12:38:12 +0200 | [diff] [blame] | 376 | def getJobDescription(jobdata) { | 
 | 377 |     def trstyle = getTrStyle(jobdata) | 
 | 378 |     def display_name = jobdata['desc'] ? "${jobdata['desc']}: ${jobdata['build_id']}" : "${jobdata['name']}: ${jobdata['build_id']}" | 
 | 379 |     if ((env.WF_SHOW_FULL_WORKFLOW_DESCRIPTION ?: false).toBoolean()) { | 
 | 380 |         display_name = "[${jobdata['name']}/${jobdata['build_id']}]: ${jobdata['desc']}" | 
 | 381 |     } | 
| AndrewB | 8505a7f | 2020-06-05 13:42:08 +0300 | [diff] [blame] | 382 |  | 
| Dennis Dmitriev | 44ad94c | 2023-11-29 12:38:12 +0200 | [diff] [blame] | 383 |     // Attach url for already built jobs | 
 | 384 |     def build_url = display_name | 
 | 385 |     if (jobdata['build_url'] != "0") { | 
 | 386 |         build_url = "<a href=${jobdata['build_url']}>$display_name</a>" | 
 | 387 |     } | 
 | 388 |  | 
 | 389 |     def status_style = getStatusStyle(jobdata['status'].toString()) | 
 | 390 |  | 
 | 391 |     return [[trstyle, build_url, jobdata['duration'], status_style,],] | 
 | 392 | } | 
 | 393 |  | 
 | 394 |  | 
 | 395 | /** | 
 | 396 |  * Update a 'script' step description | 
 | 397 |  * | 
 | 398 |  * @param jobsdata               Map with a 'script' step details and status | 
 | 399 |  */ | 
 | 400 | def getScriptDescription(jobdata) { | 
 | 401 |     def trstyle = getTrStyle(jobdata) | 
 | 402 |  | 
 | 403 |     def display_name = "${jobdata['desc']}" ?: "${jobdata['name']}" | 
 | 404 |     if ((env.WF_SHOW_FULL_WORKFLOW_DESCRIPTION ?: false).toBoolean()) { | 
 | 405 |         display_name = "[${jobdata['name']}]: ${jobdata['desc']}" | 
 | 406 |     } | 
 | 407 |  | 
 | 408 |     // Attach url for already built jobs | 
 | 409 |     def build_url = display_name | 
 | 410 |     if (jobdata['build_url'] != "0") { | 
 | 411 |         build_url = "<a href=${jobdata['build_url']}>$display_name</a>" | 
 | 412 |     } | 
 | 413 |  | 
 | 414 |     def status_style = getStatusStyle(jobdata['status'].toString()) | 
 | 415 |  | 
 | 416 |     return [[trstyle, build_url, jobdata['duration'], status_style,],] | 
 | 417 | } | 
 | 418 |  | 
 | 419 |  | 
 | 420 | /** | 
 | 421 |  * Update a 'parallel' or a 'sequence' step description | 
 | 422 |  * | 
 | 423 |  * @param jobsdata               Map with a 'together' step details and statuses | 
 | 424 |  */ | 
 | 425 | def getNestedDescription(jobdata) { | 
 | 426 |     def tableEntries = [] | 
 | 427 |     def trstyle = getTrStyle(jobdata) | 
 | 428 |  | 
 | 429 |     def display_name = "${jobdata['desc']}" ?: "${jobdata['name']}" | 
 | 430 |     if ((env.WF_SHOW_FULL_WORKFLOW_DESCRIPTION ?: false).toBoolean()) { | 
 | 431 |         display_name = "[${jobdata['name']}]: ${jobdata['desc']}" | 
 | 432 |     } | 
 | 433 |  | 
 | 434 |     // Attach url for already built jobs | 
 | 435 |     def build_url = display_name | 
 | 436 |     if (jobdata['build_url'] != "0") { | 
 | 437 |         build_url = "<a href=${jobdata['build_url']}>$display_name</a>" | 
 | 438 |     } | 
 | 439 |  | 
 | 440 |     def status_style = getStatusStyle(jobdata['status'].toString()) | 
 | 441 |  | 
 | 442 |     tableEntries += [[trstyle, build_url, jobdata['duration'], status_style,],] | 
 | 443 |  | 
 | 444 |     // Collect nested job descriptions | 
 | 445 |     for (nested_jobdata in jobdata['nested_steps_data']) { | 
 | 446 |         (nestedTableEntries, _) = getStepDescription(nested_jobdata.value) | 
 | 447 |         for (nestedTableEntry in nestedTableEntries) { | 
 | 448 |             (nested_trstyle, nested_display_name, nested_duration, nested_status_style) = nestedTableEntry | 
 | 449 |             tableEntries += [[nested_trstyle, " | ${nested_jobdata.key}: ${nested_display_name}", nested_duration, nested_status_style,],] | 
 | 450 |         } | 
 | 451 |     } | 
 | 452 |     return tableEntries | 
 | 453 | } | 
 | 454 |  | 
 | 455 |  | 
 | 456 | def getStepDescription(jobs_data) { | 
 | 457 |     def tableEntries = [] | 
 | 458 |     def child_jobs_description = '' | 
| AndrewB | 8505a7f | 2020-06-05 13:42:08 +0300 | [diff] [blame] | 459 |     for (jobdata in jobs_data) { | 
| AndrewB | 8505a7f | 2020-06-05 13:42:08 +0300 | [diff] [blame] | 460 |  | 
| Dennis Dmitriev | 44ad94c | 2023-11-29 12:38:12 +0200 | [diff] [blame] | 461 |         if (jobdata['step_key'] == 'job') { | 
 | 462 |             tableEntries += getJobDescription(jobdata) | 
| AndrewB | 8505a7f | 2020-06-05 13:42:08 +0300 | [diff] [blame] | 463 |         } | 
| Dennis Dmitriev | 44ad94c | 2023-11-29 12:38:12 +0200 | [diff] [blame] | 464 |         else if (jobdata['step_key'] == 'script') { | 
 | 465 |             tableEntries += getScriptDescription(jobdata) | 
| AndrewB | 8505a7f | 2020-06-05 13:42:08 +0300 | [diff] [blame] | 466 |         } | 
| Dennis Dmitriev | 44ad94c | 2023-11-29 12:38:12 +0200 | [diff] [blame] | 467 |         else if (jobdata['step_key'] == 'parallel' || jobdata['step_key'] == 'sequence') { | 
 | 468 |             tableEntries += getNestedDescription(jobdata) | 
 | 469 |         } | 
| AndrewB | 8505a7f | 2020-06-05 13:42:08 +0300 | [diff] [blame] | 470 |  | 
 | 471 |         // Collecting descriptions of builded child jobs | 
| Dennis Dmitriev | 44ad94c | 2023-11-29 12:38:12 +0200 | [diff] [blame] | 472 |         if (jobdata['child_desc'] != '') { | 
| AndrewB | 8505a7f | 2020-06-05 13:42:08 +0300 | [diff] [blame] | 473 |             child_jobs_description += "<b><small><a href=${jobdata['build_url']}>- ${jobdata['name']} (${jobdata['status']}):</a></small></b><br>" | 
| azvyagintsev | 0d97815 | 2022-01-27 14:01:33 +0200 | [diff] [blame] | 474 |             // remove "null" message-result from description, but leave XXX:JOBRESULT in description | 
| azvyagintsev | 8b8224d | 2023-10-06 20:52:26 +0300 | [diff] [blame] | 475 |             if (jobdata['child_desc'] != 'null') { | 
| azvyagintsev | 0d97815 | 2022-01-27 14:01:33 +0200 | [diff] [blame] | 476 |                 child_jobs_description += "<small>${jobdata['child_desc']}</small><br>" | 
 | 477 |             } | 
| AndrewB | 8505a7f | 2020-06-05 13:42:08 +0300 | [diff] [blame] | 478 |         } | 
 | 479 |     } | 
| Dennis Dmitriev | 44ad94c | 2023-11-29 12:38:12 +0200 | [diff] [blame] | 480 |     return [tableEntries, child_jobs_description] | 
 | 481 | } | 
 | 482 |  | 
 | 483 | /** | 
 | 484 |  * Update description for workflow steps | 
 | 485 |  * | 
 | 486 |  * @param jobs_data               Map with all step names and result statuses, to showing it in description | 
 | 487 |  */ | 
 | 488 | def updateDescription(jobs_data) { | 
 | 489 |     def child_jobs_description = '<strong>Descriptions from jobs:</strong><br>' | 
 | 490 |     def table_template_start = "<div><table style='border: solid 1px;'><tr><th>Job:</th><th>Duration:</th><th>Status:</th></tr>" | 
 | 491 |     def table_template_end = "</table></div>" | 
 | 492 |  | 
 | 493 |     (tableEntries, _child_jobs_description) = getStepDescription(jobs_data) | 
 | 494 |  | 
 | 495 |     def table = '' | 
 | 496 |     for (tableEntry in tableEntries) { | 
 | 497 |         // Collect table | 
 | 498 |         (trstyle, display_name, duration, status_style) = tableEntry | 
 | 499 |         table += "${trstyle}<td>${display_name}</td><td>${duration}</td>${status_style}</td></tr>" | 
 | 500 |     } | 
 | 501 |  | 
 | 502 |     child_jobs_description += _child_jobs_description | 
 | 503 |  | 
| AndrewB | 8505a7f | 2020-06-05 13:42:08 +0300 | [diff] [blame] | 504 |     currentBuild.description = table_template_start + table + table_template_end + child_jobs_description | 
 | 505 | } | 
| Dennis Dmitriev | 5d8a153 | 2019-07-30 16:39:27 +0300 | [diff] [blame] | 506 |  | 
| Dennis Dmitriev | 44ad94c | 2023-11-29 12:38:12 +0200 | [diff] [blame] | 507 |  | 
| Dennis Dmitriev | aa0fa74 | 2024-03-27 22:38:25 +0200 | [diff] [blame] | 508 | def runStep(global_variables, step, Boolean propagate = false, artifactoryBaseUrl = '', artifactoryServer = '', parent_global_variables=null) { | 
| azvyagintsev | 0d97815 | 2022-01-27 14:01:33 +0200 | [diff] [blame] | 509 |     return { | 
 | 510 |         def common = new com.mirantis.mk.Common() | 
 | 511 |         def engine = new groovy.text.GStringTemplateEngine() | 
| Dennis Dmitriev | 44ad94c | 2023-11-29 12:38:12 +0200 | [diff] [blame] | 512 |         def env_variables = common.getEnvAsMap() | 
| azvyagintsev | 0d97815 | 2022-01-27 14:01:33 +0200 | [diff] [blame] | 513 |  | 
 | 514 |         String jobDescription = step['description'] ?: '' | 
 | 515 |         def jobName = step['job'] | 
 | 516 |         def jobParameters = [:] | 
 | 517 |         def stepParameters = step['parameters'] ?: [:] | 
 | 518 |         if (step['inherit_parent_params'] ?: false) { | 
 | 519 |             // add parameters from the current job for the child job | 
 | 520 |             jobParameters << getJobDefaultParameters(env.JOB_NAME) | 
 | 521 |         } | 
 | 522 |         // add parameters from the workflow for the child job | 
 | 523 |         jobParameters << stepParameters | 
 | 524 |         def wfPauseStepBeforeRun = (step['wf_pause_step_before_run'] ?: false).toBoolean() | 
 | 525 |         def wfPauseStepTimeout = (step['wf_pause_step_timeout'] ?: 10).toInteger() | 
 | 526 |         def wfPauseStepSlackReportChannel = step['wf_pause_step_slack_report_channel'] ?: '' | 
 | 527 |  | 
 | 528 |         if (wfPauseStepBeforeRun) { | 
 | 529 |             // Try-catch construction will allow to continue Steps, if timeout reached | 
 | 530 |             try { | 
 | 531 |                 if (wfPauseStepSlackReportChannel) { | 
 | 532 |                     def slack = new com.mirantis.mcp.SlackNotification() | 
| azvyagintsev | da22aa8 | 2022-06-10 15:46:55 +0300 | [diff] [blame] | 533 |                     wfPauseStepSlackReportChannel.split(',').each { | 
 | 534 |                         slack.jobResultNotification('wf_pause_step_before_run', | 
 | 535 |                                                     it.toString(), | 
 | 536 |                                                     env.JOB_NAME, null, | 
 | 537 |                                                     env.BUILD_URL, 'slack_webhook_url') | 
 | 538 |                     } | 
| azvyagintsev | 0d97815 | 2022-01-27 14:01:33 +0200 | [diff] [blame] | 539 |                 } | 
 | 540 |                 timeout(time: wfPauseStepTimeout, unit: 'MINUTES') { | 
 | 541 |                     input("Workflow pause requested before run: ${jobName}/${jobDescription}\n" + | 
 | 542 |                       "Timeout set to ${wfPauseStepTimeout}.\n" + | 
 | 543 |                       "Do you want to proceed workflow?") | 
 | 544 |                 } | 
 | 545 |             } catch (err) { // timeout reached or input false | 
 | 546 |                 def user = err.getCauses()[0].getUser() | 
 | 547 |                 if (user.toString() != 'SYSTEM') { // SYSTEM means timeout. | 
 | 548 |                     error("Aborted after workFlow pause by: [${user}]") | 
 | 549 |                 } else { | 
 | 550 |                     common.infoMsg("Timeout finished, continue..") | 
 | 551 |                 } | 
 | 552 |             } | 
 | 553 |         } | 
 | 554 |         common.infoMsg("Attempt to run: ${jobName}/${jobDescription}") | 
 | 555 |         // Collect job parameters and run the job | 
 | 556 |         // WARN(alexz): desc must not contain invalid chars for yaml | 
 | 557 |         def jobResult = runOrGetJob(jobName, jobParameters, | 
| Dennis Dmitriev | 44ad94c | 2023-11-29 12:38:12 +0200 | [diff] [blame] | 558 |                                     global_variables, propagate, jobDescription) | 
| azvyagintsev | 0d97815 | 2022-01-27 14:01:33 +0200 | [diff] [blame] | 559 |         def buildDuration = jobResult.durationString ?: '-' | 
 | 560 |         if (buildDuration.toString() == null) { | 
 | 561 |             buildDuration = '-' | 
 | 562 |         } | 
| Dennis Dmitriev | 44ad94c | 2023-11-29 12:38:12 +0200 | [diff] [blame] | 563 |         def desc = engine.createTemplate(jobDescription.toString()).make(env_variables + global_variables) | 
 | 564 |         if ((desc.toString() == '') || (desc.toString() == 'null')) { | 
 | 565 |             desc = '' | 
 | 566 |         } | 
| azvyagintsev | 0d97815 | 2022-01-27 14:01:33 +0200 | [diff] [blame] | 567 |         def jobSummary = [ | 
 | 568 |           job_result       : jobResult.getResult().toString(), | 
 | 569 |           build_url        : jobResult.getAbsoluteUrl().toString(), | 
 | 570 |           build_id         : jobResult.getId().toString(), | 
 | 571 |           buildDuration    : buildDuration, | 
| Dennis Dmitriev | 44ad94c | 2023-11-29 12:38:12 +0200 | [diff] [blame] | 572 |           desc             : desc, | 
| azvyagintsev | 0d97815 | 2022-01-27 14:01:33 +0200 | [diff] [blame] | 573 |         ] | 
 | 574 |         def _buildDescription = jobResult.getDescription().toString() | 
| Dennis Dmitriev | 44ad94c | 2023-11-29 12:38:12 +0200 | [diff] [blame] | 575 |         if (_buildDescription) { | 
| azvyagintsev | 0d97815 | 2022-01-27 14:01:33 +0200 | [diff] [blame] | 576 |             jobSummary['build_description'] = _buildDescription | 
 | 577 |         } | 
 | 578 |         // Store links to the resulting artifacts into 'global_variables' | 
 | 579 |         storeArtifacts(jobSummary['build_url'], step['artifacts'], | 
| Dennis Dmitriev | aa0fa74 | 2024-03-27 22:38:25 +0200 | [diff] [blame] | 580 |           global_variables, jobName, jobSummary['build_id'], artifactoryBaseUrl, artifactoryServer, artifacts_msg='artifacts to local variables') | 
 | 581 |         // Store links to the resulting 'global_artifacts' into 'global_variables' | 
 | 582 |         storeArtifacts(jobSummary['build_url'], step['global_artifacts'], | 
 | 583 |           global_variables, jobName, jobSummary['build_id'], artifactoryBaseUrl, artifactoryServer, artifacts_msg='global_artifacts to local variables') | 
 | 584 |         // Store links to the resulting 'global_artifacts' into 'parent_global_variables' | 
 | 585 |         storeArtifacts(jobSummary['build_url'], step['global_artifacts'], | 
 | 586 |           parent_global_variables, jobName, jobSummary['build_id'], artifactoryBaseUrl, artifactoryServer, artifacts_msg='global_artifacts to global_variables') | 
| azvyagintsev | 0d97815 | 2022-01-27 14:01:33 +0200 | [diff] [blame] | 587 |         return jobSummary | 
 | 588 |     } | 
 | 589 | } | 
| AndrewB | 8505a7f | 2020-06-05 13:42:08 +0300 | [diff] [blame] | 590 |  | 
| Dennis Dmitriev | 44ad94c | 2023-11-29 12:38:12 +0200 | [diff] [blame] | 591 |  | 
| Dennis Dmitriev | aa0fa74 | 2024-03-27 22:38:25 +0200 | [diff] [blame] | 592 | def runScript(global_variables, step, artifactoryBaseUrl = '', artifactoryServer = '', scriptsLibrary = null, parent_global_variables=null) { | 
| Dennis Dmitriev | 44ad94c | 2023-11-29 12:38:12 +0200 | [diff] [blame] | 593 |     def common = new com.mirantis.mk.Common() | 
 | 594 |     def env_variables = common.getEnvAsMap() | 
 | 595 |  | 
 | 596 |     if (!scriptsLibrary) { | 
 | 597 |         error "'scriptsLibrary' argument is not provided to load a script object '${step['script']}' from that library" | 
 | 598 |     } | 
 | 599 |     // Evaluate the object from it's name, for example: scriptsLibrary.com.mirantis.si.runtime_steps.ParallelMkeMoskUpgradeSequences | 
 | 600 |     def scriptObj = scriptsLibrary | 
 | 601 |     for (sObj in step['script'].split("\\.")) { | 
 | 602 |         scriptObj = scriptObj."$sObj" | 
 | 603 |     } | 
 | 604 |  | 
 | 605 |     def script = scriptObj.new() | 
 | 606 |  | 
 | 607 |     def scriptSummary = [ | 
 | 608 |       job_result       : '', | 
 | 609 |       desc             : step['description'] ?: '', | 
 | 610 |     ] | 
 | 611 |  | 
 | 612 |     // prepare 'script_env' from merged 'env' and script step parameters | 
 | 613 |     def script_env = env_variables.clone() | 
 | 614 |     def stepParameters = step['parameters'] ?: [:] | 
 | 615 |     def script_parameters = generateParameters(stepParameters, global_variables) | 
 | 616 |     println "${script_parameters}" | 
 | 617 |     for (script_parameter in script_parameters) { | 
 | 618 |         common.infoMsg("Updating script env['${script_parameter.name}'] with value: ${script_parameter.value}") | 
 | 619 |         script_env[script_parameter.name] = script_parameter.value | 
 | 620 |     } | 
 | 621 |  | 
 | 622 |     try { | 
 | 623 |         script.main(this, script_env) | 
 | 624 |         scriptSummary['script_result'] = 'SUCCESS' | 
 | 625 |     } catch (InterruptedException e) { | 
 | 626 |         scriptSummary['script_result'] = 'ABORTED' | 
 | 627 |         printStackTrace(e) | 
 | 628 |     } catch (e) { | 
 | 629 |         scriptSummary['script_result'] = 'FAILURE' | 
 | 630 |         printStackTrace(e) | 
 | 631 |     } | 
 | 632 |  | 
| Dennis Dmitriev | aa0fa74 | 2024-03-27 22:38:25 +0200 | [diff] [blame] | 633 |     // Store links to the resulting 'artifacts' into 'global_variables' | 
| Dennis Dmitriev | 44ad94c | 2023-11-29 12:38:12 +0200 | [diff] [blame] | 634 |     storeArtifacts(env.BUILD_URL, step['artifacts'], | 
| Dennis Dmitriev | aa0fa74 | 2024-03-27 22:38:25 +0200 | [diff] [blame] | 635 |                    global_variables, env.JOB_NAME, env.BUILD_NUMBER, artifactoryBaseUrl, artifactoryServer, artifacts_msg='artifacts to local variables') | 
 | 636 |     // Store links to the resulting 'global_artifacts' into 'global_variables' | 
 | 637 |     storeArtifacts(env.BUILD_URL, step['global_artifacts'], | 
 | 638 |                    global_variables, env.JOB_NAME, env.BUILD_NUMBER, artifactoryBaseUrl, artifactoryServer, artifacts_msg='global_artifacts to local variables') | 
 | 639 |     // Store links to the resulting 'global_artifacts' into 'parent_global_variables' | 
 | 640 |     storeArtifacts(env.BUILD_URL, step['global_artifacts'], | 
 | 641 |                    parent_global_variables, env.JOB_NAME, env.BUILD_NUMBER, artifactoryBaseUrl, artifactoryServer, artifacts_msg='global_artifacts to global_variables') | 
| Dennis Dmitriev | 44ad94c | 2023-11-29 12:38:12 +0200 | [diff] [blame] | 642 |  | 
 | 643 |     return scriptSummary | 
 | 644 | } | 
 | 645 |  | 
 | 646 |  | 
| Dennis Dmitriev | aa0fa74 | 2024-03-27 22:38:25 +0200 | [diff] [blame] | 647 | def runParallel(global_variables, step, failed_jobs, global_jobs_data, nested_steps_data, artifactoryBaseUrl = '', artifactoryServer = '', scriptsLibrary = null, prefixMsg = '', parent_global_variables=null) { | 
| Dennis Dmitriev | 44ad94c | 2023-11-29 12:38:12 +0200 | [diff] [blame] | 648 |     // Run the specified steps in parallel | 
 | 649 |     // Repeat the steps for each parameters set from 'repeat_with_parameters_from_yaml' | 
 | 650 |     // If 'repeat_with_parameters_from_yaml' is not provided, then 'parallel' step will perform just one iteration for a default "- _FOO: _BAR" parameter | 
 | 651 |     // If 'repeat_with_parameters_from_yaml' is present, but the specified artifact contains empty list '[]', then 'parallel' step will be skipped | 
 | 652 |     // Example: | 
 | 653 |     // - parallel: | 
 | 654 |     //     - job: | 
 | 655 |     //     - job: | 
 | 656 |     //     - sequence: | 
 | 657 |     //   repeat_with_parameters_from_yaml: | 
 | 658 |     //     type: TextParameterValue | 
 | 659 |     //     get_variable_from_url: SI_PARALLEL_PARAMETERS | 
 | 660 |     //   max_concurrent: 2               # how many parallel jobs shold be run at the same time | 
 | 661 |     //   max_concurrent_interval: 300    # how many seconds should be passed between checking for an available concurrency | 
 | 662 |     //   check_failed_concurrent: false  # stop waiting for available concurrent executors if count of failed jobs >= max_concurrent, | 
 | 663 |     //                                   # which means that all available shared resources are occupied by the failed jobs | 
 | 664 |     def common = new com.mirantis.mk.Common() | 
 | 665 |  | 
 | 666 |     def sourceText = "" | 
 | 667 |     def defaultSourceText = "- _FOO: _BAR" | 
 | 668 |     if (step['repeat_with_parameters_from_yaml']) { | 
 | 669 |         def sourceParameter = ["repeat_with_parameters_from_yaml": step['repeat_with_parameters_from_yaml']] | 
 | 670 |         for (parameter in generateParameters(sourceParameter, global_variables)) { | 
 | 671 |             if (parameter.name == "repeat_with_parameters_from_yaml") { | 
 | 672 |                 sourceText = parameter.value | 
 | 673 |                 common.infoMsg("'repeat_with_parameters_from_yaml' is defined, using it as a yaml text:\n${sourceText}") | 
 | 674 |             } | 
 | 675 |         } | 
 | 676 |     } | 
 | 677 |     if (!sourceText) { | 
 | 678 |         sourceText = defaultSourceText | 
 | 679 |         common.warningMsg("'repeat_with_parameters_from_yaml' is not defined. To get one iteration, use default single entry:\n${sourceText}") | 
 | 680 |     } | 
 | 681 |     def iterateParametersList = readYaml text: sourceText | 
 | 682 |     if (!(iterateParametersList instanceof List)) { | 
 | 683 |         // Stop the pipeline if there is wrong parameters data type, to not generate parallel jobs for wrong data | 
 | 684 |         error "Expected a List in 'repeat_with_parameters_from_yaml' for 'parallel' step, but got:\n${sourceText}" | 
 | 685 |     } | 
 | 686 |  | 
 | 687 |     // Limit the maximum steps in parallel at the same time | 
 | 688 |     def max_concurrent = (step['max_concurrent'] ?: 100).toInteger() | 
 | 689 |     // Sleep for the specified amount of time until a free thread will be available | 
 | 690 |     def max_concurrent_interval = (step['max_concurrent_interval'] ?: 600).toInteger() | 
 | 691 |     // Check that failed jobs is not >= free executors. if 'true', then don't wait for free executors, fail the parallel step | 
 | 692 |     def check_failed_concurrent = (step['check_failed_concurrent'] ?: false).toBoolean() | 
 | 693 |  | 
 | 694 |     def jobs = [:] | 
 | 695 |     def nested_step_id = 0 | 
 | 696 |     def free_concurrent = max_concurrent | 
 | 697 |     def failed_concurrent = [] | 
 | 698 |  | 
 | 699 |     common.printMsg("${prefixMsg} Running parallel steps with the following parameters:\n${iterateParametersList}", "purple") | 
 | 700 |  | 
 | 701 |     for (parameters in iterateParametersList) { | 
 | 702 |         for (parallel_step in step['parallel']) { | 
 | 703 |             def step_name = "parallel#${nested_step_id}" | 
 | 704 |             def nested_step = parallel_step | 
 | 705 |             def nested_step_name = step_name | 
 | 706 |             def nested_prefix_name = "${prefixMsg}${nested_step_name} | " | 
 | 707 |  | 
 | 708 |             nested_steps_data[step_name] = [] | 
 | 709 |             prepareJobsData([nested_step,], 'parallel', nested_steps_data[step_name]) | 
 | 710 |  | 
 | 711 |             //Copy global variables and merge "parameters" dict into it for the current particular step | 
 | 712 |             def nested_global_variables = global_variables.clone() | 
 | 713 |             nested_global_variables << parameters | 
 | 714 |  | 
 | 715 |             jobs[step_name] = { | 
 | 716 |                 // initialRecurrencePeriod in milliseconds | 
 | 717 |                 waitUntil(initialRecurrencePeriod: 1500, quiet: true) { | 
 | 718 |                     if (check_failed_concurrent) { | 
 | 719 |                         if (failed_concurrent.size() >= max_concurrent){ | 
 | 720 |                             common.errorMsg("Failed jobs count is equal max_concurrent value ${max_concurrent}. Will not continue because resources are consumed") | 
 | 721 |                             error("max_concurrent == failed_concurrent") | 
 | 722 |                         } | 
 | 723 |                     } | 
 | 724 |                     if (free_concurrent > 0) { | 
 | 725 |                         free_concurrent-- | 
 | 726 |                         true | 
 | 727 |                     } else { | 
 | 728 |                         sleep(max_concurrent_interval) | 
 | 729 |                         false | 
 | 730 |                     } | 
 | 731 |                 } | 
 | 732 |  | 
 | 733 |                 try { | 
| Dennis Dmitriev | aa0fa74 | 2024-03-27 22:38:25 +0200 | [diff] [blame] | 734 |                     runWorkflowStep(nested_global_variables, nested_step, 0, nested_steps_data[nested_step_name], global_jobs_data, failed_jobs, false, artifactoryBaseUrl, artifactoryServer, scriptsLibrary, nested_prefix_name, parent_global_variables) | 
| Dennis Dmitriev | 44ad94c | 2023-11-29 12:38:12 +0200 | [diff] [blame] | 735 |                 } | 
 | 736 |                 catch (e) { | 
 | 737 |                     failed_concurrent.add(step_name) | 
 | 738 |                     throw(e) | 
 | 739 |                 } | 
 | 740 |  | 
 | 741 |                 free_concurrent++ | 
 | 742 |             } // 'jobs' closure | 
 | 743 |  | 
 | 744 |             nested_step_id++ | 
 | 745 |         } | 
 | 746 |     } | 
 | 747 |  | 
 | 748 |     def parallelSummary = [ | 
 | 749 |       nested_result       : '', | 
 | 750 |       desc                : step['description'] ?: '', | 
 | 751 |       nested_steps_data   : [:], | 
 | 752 |     ] | 
 | 753 |  | 
 | 754 |     if (iterateParametersList) { | 
 | 755 |         // Run parallel iterations | 
 | 756 |         try { | 
 | 757 |             common.infoMsg("${prefixMsg} Run steps in parallel") | 
 | 758 |  | 
 | 759 |             parallel jobs | 
 | 760 |  | 
 | 761 |             parallelSummary['nested_result'] = 'SUCCESS' | 
 | 762 |         } catch (InterruptedException e) { | 
 | 763 |             parallelSummary['nested_result'] = 'ABORTED' | 
 | 764 |             printStackTrace(e) | 
 | 765 |         } catch (e) { | 
 | 766 |             parallelSummary['nested_result'] = 'FAILURE' | 
 | 767 |             printStackTrace(e) | 
 | 768 |         } | 
 | 769 |         parallelSummary['nested_steps_data'] = nested_steps_data | 
 | 770 |     } | 
 | 771 |     else | 
 | 772 |     { | 
 | 773 |         // No parameters were provided to iterate | 
 | 774 |         common.errorMsg("${prefixMsg} No parameters were provided to iterate, skipping 'parallel' step") | 
 | 775 |         parallelSummary['nested_result'] = 'SUCCESS' | 
 | 776 |     } | 
 | 777 |     return parallelSummary | 
 | 778 | } | 
 | 779 |  | 
 | 780 |  | 
| Dennis Dmitriev | aa0fa74 | 2024-03-27 22:38:25 +0200 | [diff] [blame] | 781 | def runSequence(global_variables, step, failed_jobs, global_jobs_data, nested_steps_data, artifactoryBaseUrl = '', artifactoryServer = '', scriptsLibrary = null, prefixMsg = '', parent_global_variables=null) { | 
| Dennis Dmitriev | 44ad94c | 2023-11-29 12:38:12 +0200 | [diff] [blame] | 782 |     // Run the steps in the specified order, like in main workflow, but repeat the sequence for each parameters set from 'repeat_with_parameters_from_yaml' | 
 | 783 |     // If 'repeat_with_parameters_from_yaml' is not provided, then 'sequence' step will perform just one iteration for a default "- _FOO: _BAR" parameter | 
 | 784 |     // If 'repeat_with_parameters_from_yaml' is present, but the specified artifact contains empty list '[]', then 'sequence' step will be skipped | 
 | 785 |     // - sequence: | 
 | 786 |     //     - job: | 
 | 787 |     //     - job: | 
 | 788 |     //     - script: | 
 | 789 |     //   repeat_with_parameters_from_yaml: | 
 | 790 |     //     type: TextParameterValue | 
 | 791 |     //     get_variable_from_url: SI_PARALLEL_PARAMETERS | 
 | 792 |     def common = new com.mirantis.mk.Common() | 
 | 793 |  | 
 | 794 |     def sourceText = "" | 
 | 795 |     def defaultSourceText = "- _FOO: _BAR" | 
 | 796 |     if (step['repeat_with_parameters_from_yaml']) { | 
 | 797 |         def sourceParameter = ["repeat_with_parameters_from_yaml": step['repeat_with_parameters_from_yaml']] | 
 | 798 |         for (parameter in generateParameters(sourceParameter, global_variables)) { | 
 | 799 |             if (parameter.name == "repeat_with_parameters_from_yaml") { | 
 | 800 |                 sourceText = parameter.value | 
 | 801 |                 common.infoMsg("'repeat_with_parameters_from_yaml' is defined, using it as a yaml text:\n${sourceText}") | 
 | 802 |             } | 
 | 803 |         } | 
 | 804 |     } | 
 | 805 |     if (!sourceText) { | 
 | 806 |         sourceText = defaultSourceText | 
 | 807 |         common.warningMsg("'repeat_with_parameters_from_yaml' is not defined. To get one iteration, use default single entry:\n${sourceText}") | 
 | 808 |     } | 
 | 809 |     def iterateParametersList = readYaml text: sourceText | 
 | 810 |     if (!(iterateParametersList instanceof List)) { | 
 | 811 |         // Stop the pipeline if there is wrong parameters data type, to not generate parallel jobs for wrong data | 
 | 812 |         error "Expected a List in 'repeat_with_parameters_from_yaml' for 'sequence' step, but got:\n${sourceText}" | 
 | 813 |     } | 
 | 814 |  | 
 | 815 |     def jobs = [:] | 
 | 816 |     def nested_step_id = 0 | 
 | 817 |  | 
 | 818 |     common.printMsg("${prefixMsg} Running parallel steps with the following parameters:\n${iterateParametersList}", "purple") | 
 | 819 |  | 
 | 820 |     for (parameters in iterateParametersList) { | 
 | 821 |         def step_name = "sequence#${nested_step_id}" | 
 | 822 |         def nested_steps = step['sequence'] | 
 | 823 |         def nested_step_name = step_name | 
 | 824 |         def nested_prefix_name = "${prefixMsg}${nested_step_name} | " | 
 | 825 |  | 
 | 826 |         nested_steps_data[step_name] = [] | 
 | 827 |         prepareJobsData(nested_steps, 'sequence', nested_steps_data[step_name]) | 
 | 828 |  | 
 | 829 |         //Copy global variables and merge "parameters" dict into it for the current particular step | 
 | 830 |         def nested_global_variables = global_variables.clone() | 
 | 831 |         nested_global_variables << parameters | 
 | 832 |  | 
 | 833 |         jobs[step_name] = { | 
 | 834 |  | 
| Dennis Dmitriev | aa0fa74 | 2024-03-27 22:38:25 +0200 | [diff] [blame] | 835 |             runSteps(nested_steps, nested_global_variables, failed_jobs, nested_steps_data[nested_step_name], global_jobs_data, 0, false, artifactoryBaseUrl, artifactoryServer, scriptsLibrary, nested_prefix_name, parent_global_variables) | 
| Dennis Dmitriev | 44ad94c | 2023-11-29 12:38:12 +0200 | [diff] [blame] | 836 |  | 
 | 837 |         } // 'jobs' closure | 
 | 838 |  | 
 | 839 |         nested_step_id++ | 
 | 840 |     } | 
 | 841 |  | 
 | 842 |     def sequenceSummary = [ | 
 | 843 |       nested_result       : '', | 
 | 844 |       desc                : step['description'] ?: '', | 
 | 845 |       nested_steps_data   : [:], | 
 | 846 |     ] | 
 | 847 |  | 
 | 848 |     if (iterateParametersList) { | 
 | 849 |         // Run sequence iterations | 
 | 850 |         try { | 
 | 851 |             jobs.each { stepName, job -> | 
 | 852 |                 common.infoMsg("${prefixMsg} Running sequence ${stepName}") | 
 | 853 |                 job() | 
 | 854 |                 sleep(30) | 
 | 855 |             } | 
 | 856 |             sequenceSummary['nested_result'] = 'SUCCESS' | 
 | 857 |         } catch (InterruptedException e) { | 
 | 858 |             sequenceSummary['nested_result'] = 'ABORTED' | 
 | 859 |             printStackTrace(e) | 
 | 860 |         } catch (e) { | 
 | 861 |             sequenceSummary['nested_result'] = 'FAILURE' | 
 | 862 |             printStackTrace(e) | 
 | 863 |         } | 
 | 864 |         sequenceSummary['nested_steps_data'] = nested_steps_data | 
 | 865 |     } | 
 | 866 |     else | 
 | 867 |     { | 
 | 868 |         // No parameters were provided to iterate | 
 | 869 |         common.errorMsg("${prefixMsg} No parameters were provided to iterate, skipping 'sequence' step") | 
 | 870 |         sequenceSummary['nested_result'] = 'SUCCESS' | 
 | 871 |     } | 
 | 872 |  | 
 | 873 |     return sequenceSummary | 
 | 874 | } | 
 | 875 |  | 
 | 876 |  | 
 | 877 | def checkResult(job_result, build_url, step, failed_jobs) { | 
 | 878 |     // Check job result, in case of SUCCESS, move to next step. | 
 | 879 |     // In case job has status NOT_BUILT, fail the build or keep going depending on 'ignore_not_built' flag | 
 | 880 |     // In other cases check flag ignore_failed, if true ignore any statuses and keep going additionally | 
 | 881 |     // if skip_results is not set or set to false fail entrie workflow, otherwise succed. | 
 | 882 |     if (job_result != 'SUCCESS') { | 
 | 883 |         def ignoreStepResult = false | 
 | 884 |         switch (job_result) { | 
 | 885 |         // In cases when job was waiting too long in queue or internal job logic allows to skip building, | 
 | 886 |         // job may have NOT_BUILT status. In that case ignore_not_built flag can be used not to fail scenario. | 
 | 887 |             case "NOT_BUILT": | 
 | 888 |                 ignoreStepResult = step['ignore_not_built'] ?: false | 
 | 889 |                 break | 
 | 890 |             case "UNSTABLE": | 
 | 891 |                 ignoreStepResult = step['ignore_unstable'] ?: (step['ignore_failed'] ?: false) | 
 | 892 |                 if (ignoreStepResult && !step['skip_results'] ?: false) { | 
 | 893 |                     failed_jobs[build_url] = job_result | 
 | 894 |                 } | 
 | 895 |                 break | 
| azvyagintsev | e012e41 | 2024-05-22 16:09:23 +0300 | [diff] [blame] | 896 |             case "ABORTED": | 
 | 897 |                 ignoreStepResult = step['ignore_aborted'] ?: (step['ignore_failed'] ?: false) | 
 | 898 |                 if (ignoreStepResult && !step['skip_results'] ?: false) { | 
 | 899 |                     failed_jobs[build_url] = job_result | 
 | 900 |                 } | 
 | 901 |                 break | 
| Dennis Dmitriev | 44ad94c | 2023-11-29 12:38:12 +0200 | [diff] [blame] | 902 |             default: | 
 | 903 |                 ignoreStepResult = step['ignore_failed'] ?: false | 
 | 904 |                 if (ignoreStepResult && !step['skip_results'] ?: false) { | 
 | 905 |                     failed_jobs[build_url] = job_result | 
 | 906 |                 } | 
 | 907 |         } | 
 | 908 |         if (!ignoreStepResult) { | 
 | 909 |             currentBuild.result = job_result | 
 | 910 |             error "Job ${build_url} finished with result: ${job_result}" | 
 | 911 |         } | 
 | 912 |     } | 
 | 913 | } | 
 | 914 |  | 
| Dennis Dmitriev | aa0fa74 | 2024-03-27 22:38:25 +0200 | [diff] [blame] | 915 | def runWorkflowStep(global_variables, step, step_id, jobs_data, global_jobs_data, failed_jobs, propagate, artifactoryBaseUrl, artifactoryServer, scriptsLibrary = null, prefixMsg = '', parent_global_variables=null) { | 
| Dennis Dmitriev | 44ad94c | 2023-11-29 12:38:12 +0200 | [diff] [blame] | 916 |     def common = new com.mirantis.mk.Common() | 
 | 917 |  | 
 | 918 |     def _sep = "\n======================\n" | 
 | 919 |     if (step.containsKey('job')) { | 
 | 920 |  | 
 | 921 |         common.printMsg("${_sep}${prefixMsg}Run job ${step['job']} [at ${java.time.LocalDateTime.now()}]${_sep}", "blue") | 
 | 922 |         stage("Run job ${step['job']}") { | 
 | 923 |  | 
| Dennis Dmitriev | aa0fa74 | 2024-03-27 22:38:25 +0200 | [diff] [blame] | 924 |             def job_summary = runStep(global_variables, step, propagate, artifactoryBaseUrl, artifactoryServer, parent_global_variables).call() | 
| Dennis Dmitriev | 5d8a153 | 2019-07-30 16:39:27 +0300 | [diff] [blame] | 925 |  | 
| AndrewB | 8505a7f | 2020-06-05 13:42:08 +0300 | [diff] [blame] | 926 |             // Update jobs_data for updating description | 
| azvyagintsev | 0d97815 | 2022-01-27 14:01:33 +0200 | [diff] [blame] | 927 |             jobs_data[step_id]['build_url'] = job_summary['build_url'] | 
 | 928 |             jobs_data[step_id]['build_id'] = job_summary['build_id'] | 
 | 929 |             jobs_data[step_id]['status'] = job_summary['job_result'] | 
 | 930 |             jobs_data[step_id]['duration'] = job_summary['buildDuration'] | 
 | 931 |             jobs_data[step_id]['desc'] = job_summary['desc'] | 
 | 932 |             if (job_summary['build_description']) { | 
 | 933 |                 jobs_data[step_id]['child_desc'] = job_summary['build_description'] | 
| Dennis Dmitriev | 5d8a153 | 2019-07-30 16:39:27 +0300 | [diff] [blame] | 934 |             } | 
| azvyagintsev | 0d97815 | 2022-01-27 14:01:33 +0200 | [diff] [blame] | 935 |             def job_result = job_summary['job_result'] | 
 | 936 |             def build_url = job_summary['build_url'] | 
| Dennis Dmitriev | 44ad94c | 2023-11-29 12:38:12 +0200 | [diff] [blame] | 937 |             common.printMsg("${_sep}${prefixMsg}Job ${build_url} finished with result: ${job_result} [at ${java.time.LocalDateTime.now()}]${_sep}", "blue") | 
| azvyagintsev | 0d97815 | 2022-01-27 14:01:33 +0200 | [diff] [blame] | 938 |         } | 
| Dennis Dmitriev | 44ad94c | 2023-11-29 12:38:12 +0200 | [diff] [blame] | 939 |     } | 
 | 940 |     else if (step.containsKey('script')) { | 
 | 941 |         common.printMsg("${_sep}${prefixMsg}Run script ${step['script']} [at ${java.time.LocalDateTime.now()}]${_sep}", "blue") | 
 | 942 |         stage("Run script ${step['script']}") { | 
 | 943 |  | 
| Dennis Dmitriev | aa0fa74 | 2024-03-27 22:38:25 +0200 | [diff] [blame] | 944 |             def scriptResult = runScript(global_variables, step, artifactoryBaseUrl, artifactoryServer, scriptsLibrary, parent_global_variables) | 
| Dennis Dmitriev | 44ad94c | 2023-11-29 12:38:12 +0200 | [diff] [blame] | 945 |  | 
 | 946 |             // Use build_url just as an unique key for failed_jobs. | 
 | 947 |             // All characters after '#' are 'comment' | 
 | 948 |             def build_url = "${env.BUILD_URL}#${step_id}:${step['script']}" | 
 | 949 |             def job_result = scriptResult['script_result'] | 
 | 950 |             common.printMsg("${_sep}${prefixMsg}Script ${build_url} finished with result: ${job_result} [at ${java.time.LocalDateTime.now()}]${_sep}", "blue") | 
 | 951 |  | 
 | 952 |             jobs_data[step_id]['build_url'] = build_url | 
 | 953 |             jobs_data[step_id]['status'] = scriptResult['script_result'] | 
 | 954 |             jobs_data[step_id]['desc'] = scriptResult['desc'] | 
 | 955 |             if (scriptResult['build_description']) { | 
 | 956 |                 jobs_data[step_id]['child_desc'] = scriptResult['build_description'] | 
 | 957 |             } | 
 | 958 |         } | 
 | 959 |     } | 
 | 960 |     else if (step.containsKey('parallel')) { | 
 | 961 |         common.printMsg("${_sep}${prefixMsg}Run steps in parallel [at ${java.time.LocalDateTime.now()}]:${_sep}", "blue") | 
 | 962 |         stage("Run steps in parallel:") { | 
 | 963 |  | 
 | 964 |             // Allocate a map to collect nested steps data for updateDescription() | 
 | 965 |             def nested_steps_data = [:] | 
 | 966 |             jobs_data[step_id]['nested_steps_data'] = nested_steps_data | 
 | 967 |  | 
| Dennis Dmitriev | aa0fa74 | 2024-03-27 22:38:25 +0200 | [diff] [blame] | 968 |             def parallelResult = runParallel(global_variables, step, failed_jobs, global_jobs_data, nested_steps_data, artifactoryBaseUrl, artifactoryServer, scriptsLibrary, prefixMsg, parent_global_variables) | 
| Dennis Dmitriev | 44ad94c | 2023-11-29 12:38:12 +0200 | [diff] [blame] | 969 |  | 
 | 970 |             // Use build_url just as an unique key for failed_jobs. | 
 | 971 |             // All characters after '#' are 'comment' | 
 | 972 |             def build_url = "${env.BUILD_URL}#${step_id}" | 
 | 973 |             def job_result = parallelResult['nested_result'] | 
 | 974 |             common.printMsg("${_sep}${prefixMsg}Parallel steps ${build_url} finished with result: ${job_result} [at ${java.time.LocalDateTime.now()}]${_sep}", "blue") | 
 | 975 |  | 
 | 976 |             jobs_data[step_id]['build_url'] = build_url | 
 | 977 |             jobs_data[step_id]['status'] = parallelResult['nested_result'] | 
 | 978 |             jobs_data[step_id]['desc'] = parallelResult['desc'] | 
 | 979 |             if (parallelResult['build_description']) { | 
 | 980 |                 jobs_data[step_id]['child_desc'] = parallelResult['build_description'] | 
 | 981 |             } | 
 | 982 |         } | 
 | 983 |     } | 
 | 984 |     else if (step.containsKey('sequence')) { | 
 | 985 |         common.printMsg("${_sep}${prefixMsg}Run steps in sequence [at ${java.time.LocalDateTime.now()}]:${_sep}", "blue") | 
 | 986 |         stage("Run steps in sequence:") { | 
 | 987 |  | 
 | 988 |             // Allocate a map to collect nested steps data for updateDescription() | 
 | 989 |             def nested_steps_data = [:] | 
 | 990 |             jobs_data[step_id]['nested_steps_data'] = nested_steps_data | 
 | 991 |  | 
| Dennis Dmitriev | aa0fa74 | 2024-03-27 22:38:25 +0200 | [diff] [blame] | 992 |             def sequenceResult = runSequence(global_variables, step, failed_jobs, global_jobs_data, nested_steps_data, artifactoryBaseUrl, artifactoryServer, scriptsLibrary, prefixMsg, parent_global_variables) | 
| Dennis Dmitriev | 44ad94c | 2023-11-29 12:38:12 +0200 | [diff] [blame] | 993 |  | 
 | 994 |             // Use build_url just as an unique key for failed_jobs. | 
 | 995 |             // All characters after '#' are 'comment' | 
 | 996 |             def build_url = "${env.BUILD_URL}#${step_id}" | 
 | 997 |             def job_result = sequenceResult['nested_result'] | 
 | 998 |             common.printMsg("${_sep}${prefixMsg}Sequence steps ${build_url} finished with result: ${job_result} [at ${java.time.LocalDateTime.now()}]${_sep}", "blue") | 
 | 999 |  | 
 | 1000 |             jobs_data[step_id]['build_url'] = build_url | 
 | 1001 |             jobs_data[step_id]['status'] = sequenceResult['nested_result'] | 
 | 1002 |             jobs_data[step_id]['desc'] = sequenceResult['desc'] | 
 | 1003 |             if (sequenceResult['build_description']) { | 
 | 1004 |                 jobs_data[step_id]['child_desc'] = sequenceResult['build_description'] | 
 | 1005 |             } | 
 | 1006 |         } | 
 | 1007 |     } | 
 | 1008 |  | 
 | 1009 |     updateDescription(global_jobs_data) | 
 | 1010 |  | 
 | 1011 |     job_result = jobs_data[step_id]['status'] | 
 | 1012 |     checkResult(job_result, build_url, step, failed_jobs) | 
 | 1013 |  | 
 | 1014 | //    return build_url | 
 | 1015 |  | 
 | 1016 | } | 
 | 1017 |  | 
 | 1018 | /** | 
 | 1019 |  * Run the workflow or final steps one by one | 
 | 1020 |  * | 
 | 1021 |  * @param steps                   List of steps (Jenkins jobs) to execute | 
 | 1022 |  * @param global_variables        Map where the collected artifact URLs and 'env' objects are stored | 
 | 1023 |  * @param failed_jobs             Map with failed job names and result statuses, to report it later | 
 | 1024 |  * @param jobs_data               Map with all job names and result statuses, to showing it in description | 
 | 1025 |  * @param step_id                 Counter for matching step ID with cell ID in description table | 
 | 1026 |  * @param propagate               Boolean. If false: allows to collect artifacts after job is finished, even with FAILURE status | 
 | 1027 |  *                                If true: immediatelly fails the pipeline. DO NOT USE 'true' with runScenario(). | 
 | 1028 |  */ | 
| Dennis Dmitriev | aa0fa74 | 2024-03-27 22:38:25 +0200 | [diff] [blame] | 1029 | def runSteps(steps, global_variables, failed_jobs, jobs_data, global_jobs_data, step_id, Boolean propagate = false, artifactoryBaseUrl = '', artifactoryServer = '', scriptsLibrary = null, prefixMsg = '', parent_global_variables=null) { | 
| Dennis Dmitriev | 44ad94c | 2023-11-29 12:38:12 +0200 | [diff] [blame] | 1030 |     // Show expected jobs list in description | 
 | 1031 |     updateDescription(global_jobs_data) | 
 | 1032 |  | 
 | 1033 |     for (step in steps) { | 
 | 1034 |  | 
| Dennis Dmitriev | aa0fa74 | 2024-03-27 22:38:25 +0200 | [diff] [blame] | 1035 |         runWorkflowStep(global_variables, step, step_id, jobs_data, global_jobs_data, failed_jobs, propagate, artifactoryBaseUrl, artifactoryServer, scriptsLibrary, prefixMsg, parent_global_variables) | 
| Dennis Dmitriev | 44ad94c | 2023-11-29 12:38:12 +0200 | [diff] [blame] | 1036 |  | 
| azvyagintsev | 75390d9 | 2021-04-12 14:20:11 +0300 | [diff] [blame] | 1037 |         // Jump to next ID for updating next job data in description table | 
 | 1038 |         step_id++ | 
| azvyagintsev | 0d97815 | 2022-01-27 14:01:33 +0200 | [diff] [blame] | 1039 |     } | 
| Dennis Dmitriev | 5d8a153 | 2019-07-30 16:39:27 +0300 | [diff] [blame] | 1040 | } | 
 | 1041 |  | 
| Dennis Dmitriev | 44ad94c | 2023-11-29 12:38:12 +0200 | [diff] [blame] | 1042 |  | 
 | 1043 | /** | 
 | 1044 |  * Prepare jobs_data for generating the scenario description | 
 | 1045 |  */ | 
 | 1046 | def prepareJobsData(scenario_steps, step_type, jobs_data) { | 
 | 1047 |     def list_id = jobs_data.size() | 
 | 1048 |  | 
 | 1049 |     for (step in scenario_steps) { | 
 | 1050 |         def display_name = '' | 
 | 1051 |         def step_key = '' | 
 | 1052 |         def desc = '' | 
 | 1053 |  | 
 | 1054 |         if (step.containsKey('job')) { | 
 | 1055 |             display_name = step['job'] | 
 | 1056 |             step_key = 'job' | 
 | 1057 |         } | 
 | 1058 |         else if (step.containsKey('script')) { | 
 | 1059 |             display_name = step['script'] | 
 | 1060 |             step_key = 'script' | 
 | 1061 |         } | 
 | 1062 |         else if (step.containsKey('parallel')) { | 
 | 1063 |             display_name = 'Parallel steps' | 
 | 1064 |             step_key = 'parallel' | 
 | 1065 |         } | 
 | 1066 |         else if (step.containsKey('sequence')) { | 
 | 1067 |             display_name = 'Sequence steps' | 
 | 1068 |             step_key = 'sequence' | 
 | 1069 |         } | 
 | 1070 |  | 
 | 1071 |         if (step['description'] != null && step['description'] != 'null' && step['description'].toString() != '') { | 
 | 1072 |             desc = (step['description'] ?: '').toString() | 
 | 1073 |         } | 
 | 1074 |  | 
 | 1075 |         jobs_data.add([list_id      : "$list_id", | 
 | 1076 |                        type         : step_type, | 
 | 1077 |                        name         : "$display_name", | 
 | 1078 |                        build_url    : "0", | 
 | 1079 |                        build_id     : "-", | 
 | 1080 |                        status       : "-", | 
 | 1081 |                        desc         : desc, | 
 | 1082 |                        child_desc   : "", | 
 | 1083 |                        duration     : '-', | 
 | 1084 |                        step_key     : step_key, | 
 | 1085 |                        together_steps: [], | 
 | 1086 |                       ]) | 
 | 1087 |         list_id += 1 | 
 | 1088 |     } | 
 | 1089 | } | 
 | 1090 |  | 
 | 1091 |  | 
| Dennis Dmitriev | 5d8a153 | 2019-07-30 16:39:27 +0300 | [diff] [blame] | 1092 | /** | 
 | 1093 |  * Run the workflow scenario | 
 | 1094 |  * | 
 | 1095 |  * @param scenario: Map with scenario steps. | 
 | 1096 |  | 
 | 1097 |  * There are two keys in the scenario: | 
 | 1098 |  *   workflow: contains steps to run deploy and test jobs | 
 | 1099 |  *   finally: contains steps to run report and cleanup jobs | 
 | 1100 |  * | 
 | 1101 |  * Scenario execution example: | 
 | 1102 |  * | 
 | 1103 |  *     scenario_yaml = """\ | 
 | 1104 |  *     workflow: | 
 | 1105 |  *     - job: deploy-kaas | 
 | 1106 |  *       ignore_failed: false | 
| AndrewB | 8505a7f | 2020-06-05 13:42:08 +0300 | [diff] [blame] | 1107 |  *       description: "Management cluster ${KAAS_VERSION}" | 
| Dennis Dmitriev | 5d8a153 | 2019-07-30 16:39:27 +0300 | [diff] [blame] | 1108 |  *       parameters: | 
 | 1109 |  *         KAAS_VERSION: | 
 | 1110 |  *           type: StringParameterValue | 
 | 1111 |  *           use_variable: KAAS_VERSION | 
 | 1112 |  *       artifacts: | 
 | 1113 |  *         KUBECONFIG_ARTIFACT: artifacts/management_kubeconfig | 
| Dennis Dmitriev | cae9bca | 2019-09-19 16:10:03 +0300 | [diff] [blame] | 1114 |  *         DEPLOYED_KAAS_VERSION: artifacts/management_version | 
| Dennis Dmitriev | 5d8a153 | 2019-07-30 16:39:27 +0300 | [diff] [blame] | 1115 |  * | 
| Dennis Dmitriev | 5f014d8 | 2020-04-29 00:00:34 +0300 | [diff] [blame] | 1116 |  *     - job: create-child | 
 | 1117 |  *       inherit_parent_params: true | 
 | 1118 |  *       ignore_failed: false | 
 | 1119 |  *       parameters: | 
 | 1120 |  *         KUBECONFIG_ARTIFACT_URL: | 
 | 1121 |  *           type: StringParameterValue | 
 | 1122 |  *           use_variable: KUBECONFIG_ARTIFACT | 
 | 1123 |  *         KAAS_VERSION: | 
 | 1124 |  *           type: StringParameterValue | 
 | 1125 |  *           get_variable_from_url: DEPLOYED_KAAS_VERSION | 
| Dennis Dmitriev | 6c355be | 2021-11-09 14:06:56 +0200 | [diff] [blame] | 1126 |  *         RELEASE_NAME: | 
 | 1127 |  *           type: StringParameterValue | 
 | 1128 |  *           get_variable_from_yaml: | 
 | 1129 |  *               yaml_url: SI_CONFIG_ARTIFACT | 
 | 1130 |  *               yaml_key: .clusters[0].release_name | 
| Dennis Dmitriev | aa0fa74 | 2024-03-27 22:38:25 +0200 | [diff] [blame] | 1131 |  *       global_artifacts: | 
 | 1132 |  *         CHILD_CONFIG_1: artifacts/child_kubeconfig | 
| Dennis Dmitriev | 5f014d8 | 2020-04-29 00:00:34 +0300 | [diff] [blame] | 1133 |  * | 
| Dennis Dmitriev | 5d8a153 | 2019-07-30 16:39:27 +0300 | [diff] [blame] | 1134 |  *     - job: test-kaas-ui | 
| Mykyta Karpin | a3d775e | 2020-04-24 14:45:17 +0300 | [diff] [blame] | 1135 |  *       ignore_not_built: false | 
| Dennis Dmitriev | 5d8a153 | 2019-07-30 16:39:27 +0300 | [diff] [blame] | 1136 |  *       parameters: | 
 | 1137 |  *         KUBECONFIG_ARTIFACT_URL: | 
 | 1138 |  *           type: StringParameterValue | 
 | 1139 |  *           use_variable: KUBECONFIG_ARTIFACT | 
| Dennis Dmitriev | cae9bca | 2019-09-19 16:10:03 +0300 | [diff] [blame] | 1140 |  *         KAAS_VERSION: | 
 | 1141 |  *           type: StringParameterValue | 
 | 1142 |  *           get_variable_from_url: DEPLOYED_KAAS_VERSION | 
| Dennis Dmitriev | 5d8a153 | 2019-07-30 16:39:27 +0300 | [diff] [blame] | 1143 |  *       artifacts: | 
 | 1144 |  *         REPORT_SI_KAAS_UI: artifacts/test_kaas_ui_result.xml | 
| Dennis Dmitriev | 5d8a153 | 2019-07-30 16:39:27 +0300 | [diff] [blame] | 1145 |  *     finally: | 
 | 1146 |  *     - job: testrail-report | 
 | 1147 |  *       ignore_failed: true | 
 | 1148 |  *       parameters: | 
| Dennis Dmitriev | ce47093 | 2019-09-18 18:31:11 +0300 | [diff] [blame] | 1149 |  *         KAAS_VERSION: | 
| Dennis Dmitriev | 5d8a153 | 2019-07-30 16:39:27 +0300 | [diff] [blame] | 1150 |  *           type: StringParameterValue | 
| Dennis Dmitriev | cae9bca | 2019-09-19 16:10:03 +0300 | [diff] [blame] | 1151 |  *           get_variable_from_url: DEPLOYED_KAAS_VERSION | 
| Dennis Dmitriev | ce47093 | 2019-09-18 18:31:11 +0300 | [diff] [blame] | 1152 |  *         REPORTS_LIST: | 
 | 1153 |  *           type: TextParameterValue | 
 | 1154 |  *           use_template: | | 
 | 1155 |  *             REPORT_SI_KAAS_UI: \$REPORT_SI_KAAS_UI | 
| Dennis Dmitriev | 5d8a153 | 2019-07-30 16:39:27 +0300 | [diff] [blame] | 1156 |  *     """ | 
 | 1157 |  * | 
 | 1158 |  *     runScenario(scenario) | 
 | 1159 |  * | 
| Dennis Dmitriev | 5f014d8 | 2020-04-29 00:00:34 +0300 | [diff] [blame] | 1160 |  * Scenario workflow keys: | 
 | 1161 |  * | 
 | 1162 |  *   job: string. Jenkins job name | 
 | 1163 |  *   ignore_failed: bool. if true, keep running the workflow jobs if the job is failed, but fail the workflow at finish | 
| Sergey Lalov | 3a2e790 | 2023-07-27 01:19:02 +0400 | [diff] [blame] | 1164 |  *   ignore_unstable: bool. if true, keep running the workflow jobs if the job is unstable, but mark the workflow is unstable at finish | 
| azvyagintsev | e012e41 | 2024-05-22 16:09:23 +0300 | [diff] [blame] | 1165 |  *   ignore_aborted: bool. if true, keep running the workflow jobs if the job is aborted, but mark the workflow is unstable at finish | 
| Vasyl Saienko | e72b994 | 2021-03-04 10:54:49 +0200 | [diff] [blame] | 1166 |  *   skip_results: bool. if true, keep running the workflow jobs if the job is failed, but do not fail the workflow at finish. Makes sense only when ignore_failed is set. | 
| Dennis Dmitriev | 5f014d8 | 2020-04-29 00:00:34 +0300 | [diff] [blame] | 1167 |  *   ignore_not_built: bool. if true, keep running the workflow jobs if the job set own status to NOT_BUILT, do not fail the workflow at finish for such jobs | 
 | 1168 |  *   inherit_parent_params: bool. if true, provide all parameters from the parent job to the child job as defaults | 
 | 1169 |  *   parameters: dict. parameters name and type to inherit from parent to child job, or from artifact to child job | 
| azvyagintsev | b3cd2a7 | 2022-01-17 23:41:34 +0200 | [diff] [blame] | 1170 |  *   wf_pause_step_before_run: bool. Interactive pause exact step before run. | 
 | 1171 |  *   wf_pause_step_slack_report_channel: If step paused, send message about it in slack. | 
 | 1172 |  *   wf_pause_step_timeout: timeout im minutes to wait for manual unpause. | 
| Dennis Dmitriev | 5d8a153 | 2019-07-30 16:39:27 +0300 | [diff] [blame] | 1173 |  */ | 
| Dennis Dmitriev | 44ad94c | 2023-11-29 12:38:12 +0200 | [diff] [blame] | 1174 | def runScenario(scenario, slackReportChannel = '', artifactoryBaseUrl = '', Boolean logGlobalVariables = false, artifactoryServer = '', scriptsLibrary = null, | 
 | 1175 |                 global_variables = null, failed_jobs = null, jobs_data = null) { | 
 | 1176 |     def common = new com.mirantis.mk.Common() | 
| Dennis Dmitriev | 5d8a153 | 2019-07-30 16:39:27 +0300 | [diff] [blame] | 1177 |  | 
| Dennis Dmitriev | 79f3a2d | 2019-08-09 16:06:00 +0300 | [diff] [blame] | 1178 |     // Clear description before adding new messages | 
 | 1179 |     currentBuild.description = '' | 
| Dennis Dmitriev | 5d8a153 | 2019-07-30 16:39:27 +0300 | [diff] [blame] | 1180 |     // Collect the parameters for the jobs here | 
| Dennis Dmitriev | 44ad94c | 2023-11-29 12:38:12 +0200 | [diff] [blame] | 1181 |     if (global_variables == null) { | 
 | 1182 |         global_variables = [:] | 
 | 1183 |     } | 
| Dennis Dmitriev | 5d8a153 | 2019-07-30 16:39:27 +0300 | [diff] [blame] | 1184 |     // List of failed jobs to show at the end | 
| Dennis Dmitriev | 44ad94c | 2023-11-29 12:38:12 +0200 | [diff] [blame] | 1185 |     if (failed_jobs == null) { | 
 | 1186 |         failed_jobs = [:] | 
 | 1187 |     } | 
| AndrewB | 8505a7f | 2020-06-05 13:42:08 +0300 | [diff] [blame] | 1188 |     // Jobs data to use for wf job build description | 
| Dennis Dmitriev | 44ad94c | 2023-11-29 12:38:12 +0200 | [diff] [blame] | 1189 |     if (jobs_data == null) { | 
 | 1190 |         jobs_data = [] | 
 | 1191 |     } | 
 | 1192 |     def global_jobs_data = jobs_data | 
 | 1193 |  | 
| AndrewB | 8505a7f | 2020-06-05 13:42:08 +0300 | [diff] [blame] | 1194 |     // Counter for matching step ID with cell ID in description table | 
| Dennis Dmitriev | 44ad94c | 2023-11-29 12:38:12 +0200 | [diff] [blame] | 1195 |     def step_id = jobs_data.size() | 
| AndrewB | 8505a7f | 2020-06-05 13:42:08 +0300 | [diff] [blame] | 1196 |     // Generate expected list jobs for description | 
| Dennis Dmitriev | 44ad94c | 2023-11-29 12:38:12 +0200 | [diff] [blame] | 1197 |     prepareJobsData(scenario['workflow'], 'workflow', jobs_data) | 
| azvyagintsev | 0d97815 | 2022-01-27 14:01:33 +0200 | [diff] [blame] | 1198 |  | 
| Dennis Dmitriev | 44ad94c | 2023-11-29 12:38:12 +0200 | [diff] [blame] | 1199 |     def pause_step_id = jobs_data.size() | 
 | 1200 |     // Generate expected list jobs for description | 
 | 1201 |     prepareJobsData(scenario['pause'], 'pause', jobs_data) | 
| Sergey Lalov | 702384d | 2022-11-10 12:10:23 +0400 | [diff] [blame] | 1202 |  | 
| Dennis Dmitriev | 44ad94c | 2023-11-29 12:38:12 +0200 | [diff] [blame] | 1203 |     def finally_step_id = jobs_data.size() | 
 | 1204 |     // Generate expected list jobs for description | 
 | 1205 |     prepareJobsData(scenario['finally'], 'finally', jobs_data) | 
 | 1206 |  | 
 | 1207 |  | 
| Sergey Lalov | 702384d | 2022-11-10 12:10:23 +0400 | [diff] [blame] | 1208 |     def job_failed_flag = false | 
| Dennis Dmitriev | 5d8a153 | 2019-07-30 16:39:27 +0300 | [diff] [blame] | 1209 |     try { | 
 | 1210 |         // Run the 'workflow' jobs | 
| Dennis Dmitriev | aa0fa74 | 2024-03-27 22:38:25 +0200 | [diff] [blame] | 1211 |         runSteps(scenario['workflow'], global_variables, failed_jobs, jobs_data, global_jobs_data, step_id, false, artifactoryBaseUrl, artifactoryServer, scriptsLibrary, '', global_variables) | 
| Dennis Dmitriev | 44ad94c | 2023-11-29 12:38:12 +0200 | [diff] [blame] | 1212 |     } catch (InterruptedException e) { | 
| Sergey Lalov | 702384d | 2022-11-10 12:10:23 +0400 | [diff] [blame] | 1213 |         job_failed_flag = true | 
| Dennis Dmitriev | 5d8a153 | 2019-07-30 16:39:27 +0300 | [diff] [blame] | 1214 |         error "The job was aborted" | 
| Dennis Dmitriev | 5d8a153 | 2019-07-30 16:39:27 +0300 | [diff] [blame] | 1215 |     } catch (e) { | 
| Sergey Lalov | 702384d | 2022-11-10 12:10:23 +0400 | [diff] [blame] | 1216 |         job_failed_flag = true | 
| Dennis Dmitriev | 44ad94c | 2023-11-29 12:38:12 +0200 | [diff] [blame] | 1217 |         printStackTrace(e) | 
| Dennis Dmitriev | 5d8a153 | 2019-07-30 16:39:27 +0300 | [diff] [blame] | 1218 |         error("Build failed: " + e.toString()) | 
| Sergey Lalov | 702384d | 2022-11-10 12:10:23 +0400 | [diff] [blame] | 1219 |  | 
| Dennis Dmitriev | 5d8a153 | 2019-07-30 16:39:27 +0300 | [diff] [blame] | 1220 |     } finally { | 
| Dennis Dmitriev | 38a45cd | 2023-02-27 14:22:13 +0200 | [diff] [blame] | 1221 |         // Log global_variables | 
 | 1222 |         if (logGlobalVariables) { | 
 | 1223 |             printVariables(global_variables) | 
 | 1224 |         } | 
 | 1225 |  | 
| Dennis Dmitriev | 44ad94c | 2023-11-29 12:38:12 +0200 | [diff] [blame] | 1226 |         def flag_pause_variable = (env.PAUSE_FOR_DEBUG) != null | 
| Sergey Lalov | 702384d | 2022-11-10 12:10:23 +0400 | [diff] [blame] | 1227 |         // Run the 'finally' or 'pause' jobs | 
| Sergey Lalov | 6e9400c | 2022-11-17 12:59:31 +0400 | [diff] [blame] | 1228 |         common.infoMsg(failed_jobs) | 
| Sergey Lalov | 2d1cd9c | 2023-08-03 17:08:09 +0400 | [diff] [blame] | 1229 |         // Run only if there are failed jobs in the scenario | 
 | 1230 |         if (flag_pause_variable && (PAUSE_FOR_DEBUG && job_failed_flag)) { | 
| Sergey Lalov | 702384d | 2022-11-10 12:10:23 +0400 | [diff] [blame] | 1231 |             // Switching to 'pause' step index | 
 | 1232 |             common.infoMsg("FINALLY BLOCK - PAUSE") | 
 | 1233 |             step_id = pause_step_id | 
| Dennis Dmitriev | aa0fa74 | 2024-03-27 22:38:25 +0200 | [diff] [blame] | 1234 |             runSteps(scenario['pause'], global_variables, failed_jobs, jobs_data, global_jobs_data, step_id, false, artifactoryBaseUrl, artifactoryServer, scriptsLibrary, '', global_variables) | 
| Sergey Lalov | 702384d | 2022-11-10 12:10:23 +0400 | [diff] [blame] | 1235 |  | 
 | 1236 |         } | 
 | 1237 |          // Switching to 'finally' step index | 
 | 1238 |         common.infoMsg("FINALLY BLOCK - CLEAR") | 
| AndrewB | 8505a7f | 2020-06-05 13:42:08 +0300 | [diff] [blame] | 1239 |         step_id = finally_step_id | 
| Dennis Dmitriev | aa0fa74 | 2024-03-27 22:38:25 +0200 | [diff] [blame] | 1240 |         runSteps(scenario['finally'], global_variables, failed_jobs, jobs_data, global_jobs_data, step_id, false, artifactoryBaseUrl, artifactoryServer, scriptsLibrary, '', global_variables) | 
| Dennis Dmitriev | 5d8a153 | 2019-07-30 16:39:27 +0300 | [diff] [blame] | 1241 |  | 
 | 1242 |         if (failed_jobs) { | 
| azvyagintsev | 0d97815 | 2022-01-27 14:01:33 +0200 | [diff] [blame] | 1243 |             def statuses = [] | 
| sgudz | 9ac09d2 | 2020-01-22 14:31:30 +0200 | [diff] [blame] | 1244 |             failed_jobs.each { | 
| sgudz | 74c8cdd | 2020-01-23 14:26:32 +0200 | [diff] [blame] | 1245 |                 statuses += it.value | 
| azvyagintsev | 75390d9 | 2021-04-12 14:20:11 +0300 | [diff] [blame] | 1246 |             } | 
| sgudz | 9ac09d2 | 2020-01-22 14:31:30 +0200 | [diff] [blame] | 1247 |             if (statuses.contains('FAILURE')) { | 
 | 1248 |                 currentBuild.result = 'FAILURE' | 
| Sergey Lalov | e5e0a84 | 2023-10-02 15:55:59 +0400 | [diff] [blame] | 1249 |             } else if (statuses.contains('ABORTED')) { | 
 | 1250 |                 currentBuild.result = 'ABORTED' | 
| Dennis Dmitriev | 44ad94c | 2023-11-29 12:38:12 +0200 | [diff] [blame] | 1251 |             } else if (statuses.contains('UNSTABLE')) { | 
| sgudz | 9ac09d2 | 2020-01-22 14:31:30 +0200 | [diff] [blame] | 1252 |                 currentBuild.result = 'UNSTABLE' | 
| azvyagintsev | 75390d9 | 2021-04-12 14:20:11 +0300 | [diff] [blame] | 1253 |             } else { | 
| sgudz | 9ac09d2 | 2020-01-22 14:31:30 +0200 | [diff] [blame] | 1254 |                 currentBuild.result = 'FAILURE' | 
 | 1255 |             } | 
| Dennis Dmitriev | 5d8a153 | 2019-07-30 16:39:27 +0300 | [diff] [blame] | 1256 |             println "Failed jobs: ${failed_jobs}" | 
| vnaumov | 68cba27 | 2020-05-20 11:24:02 +0200 | [diff] [blame] | 1257 |         } else { | 
 | 1258 |             currentBuild.result = 'SUCCESS' | 
| Dennis Dmitriev | 5d8a153 | 2019-07-30 16:39:27 +0300 | [diff] [blame] | 1259 |         } | 
| vnaumov | 5a6eb8a | 2020-03-31 11:16:54 +0200 | [diff] [blame] | 1260 |  | 
| Sergey Lalov | 3a2e790 | 2023-07-27 01:19:02 +0400 | [diff] [blame] | 1261 |         common.infoMsg("Workflow finished with result: ${currentBuild.result}") | 
 | 1262 |  | 
| vnaumov | 5a6eb8a | 2020-03-31 11:16:54 +0200 | [diff] [blame] | 1263 |         if (slackReportChannel) { | 
 | 1264 |             def slack = new com.mirantis.mcp.SlackNotification() | 
 | 1265 |             slack.jobResultNotification(currentBuild.result, slackReportChannel, '', null, '', 'slack_webhook_url') | 
 | 1266 |         } | 
| sgudz | 9ac09d2 | 2020-01-22 14:31:30 +0200 | [diff] [blame] | 1267 |     } // finally | 
| Dennis Dmitriev | 5d8a153 | 2019-07-30 16:39:27 +0300 | [diff] [blame] | 1268 | } | 
| Dennis Dmitriev | 44ad94c | 2023-11-29 12:38:12 +0200 | [diff] [blame] | 1269 |  | 
 | 1270 |  | 
 | 1271 | def manageArtifacts(entrypointDirectory, storeArtsInJenkins = false, artifactoryServerName = 'mcp-ci') { | 
 | 1272 |     def mcpArtifactory = new com.mirantis.mcp.MCPArtifactory() | 
 | 1273 |     def artifactoryRepoPath = "si-local/jenkins-job-artifacts/${JOB_NAME}/${BUILD_NUMBER}" | 
 | 1274 |     def tests_log = "${entrypointDirectory}/tests.log" | 
 | 1275 |  | 
 | 1276 |     if (fileExists(tests_log)) { | 
 | 1277 |         try { | 
 | 1278 |             def size = sh([returnStdout: true, script: "stat --printf='%s' ${tests_log}"]).trim().toInteger() | 
 | 1279 |             // do not archive unless it is more than 50 MB | 
 | 1280 |             def allowed_size = 1048576 * 50 | 
 | 1281 |             if (size >= allowed_size) { | 
 | 1282 |                 sh("gzip ${tests_log} || true") | 
 | 1283 |             } | 
 | 1284 |         } catch (e) { | 
 | 1285 |             print("Cannot determine tests.log filesize: ${e}") | 
 | 1286 |         } | 
 | 1287 |     } | 
 | 1288 |  | 
 | 1289 |     if (storeArtsInJenkins) { | 
 | 1290 |         archiveArtifacts( | 
 | 1291 |             artifacts: "${entrypointDirectory}/**", | 
 | 1292 |             allowEmptyArchive: true | 
 | 1293 |         ) | 
 | 1294 |     } | 
 | 1295 |     artConfig = [ | 
 | 1296 |         deleteArtifacts: false, | 
 | 1297 |         artifactory    : artifactoryServerName, | 
 | 1298 |         artifactPattern: "${entrypointDirectory}/**", | 
 | 1299 |         artifactoryRepo: "artifactory/${artifactoryRepoPath}", | 
 | 1300 |     ] | 
 | 1301 |     def artDescription = mcpArtifactory.uploadArtifactsToArtifactory(artConfig) | 
 | 1302 |     currentBuild.description += "${artDescription}<br>" | 
 | 1303 |  | 
 | 1304 |     junit(testResults: "${entrypointDirectory}/**/*.xml", allowEmptyResults: true) | 
 | 1305 |  | 
 | 1306 |     def artifactoryServer = Artifactory.server(artifactoryServerName) | 
 | 1307 |     def artifactsUrl = "${artifactoryServer.getUrl()}/artifactory/${artifactoryRepoPath}" | 
 | 1308 |     return artifactsUrl | 
 | 1309 | } | 
 | 1310 |  | 
 | 1311 |  | 
 | 1312 | return this |