| Sergey Kolekonov | ba20398 | 2016-12-21 18:32:17 +0400 | [diff] [blame] | 1 | package com.mirantis.mk | 
 | 2 |  | 
 | 3 | /** | 
 | 4 |  * | 
 | 5 |  * Python functions | 
 | 6 |  * | 
 | 7 |  */ | 
 | 8 |  | 
 | 9 | /** | 
 | 10 |  * Install python virtualenv | 
 | 11 |  * | 
| Vladislav Naumov | 1110386 | 2017-07-19 17:02:39 +0300 | [diff] [blame] | 12 |  * @param path          Path to virtualenv | 
 | 13 |  * @param python        Version of Python (python/python3) | 
 | 14 |  * @param reqs          Environment requirements in list format | 
 | 15 |  * @param reqs_path     Environment requirements path in str format | 
| Sergey Kolekonov | ba20398 | 2016-12-21 18:32:17 +0400 | [diff] [blame] | 16 |  */ | 
| Jakub Josef | 996f4ef | 2017-10-24 13:20:43 +0200 | [diff] [blame] | 17 | def setupVirtualenv(path, python = 'python2', reqs=[], reqs_path=null, clean=false, useSystemPackages=false) { | 
| Tomáš Kukrál | 69c2545 | 2017-07-27 14:59:40 +0200 | [diff] [blame] | 18 |     def common = new com.mirantis.mk.Common() | 
 | 19 |  | 
| Jakub Josef | 87a8a3c | 2018-01-26 12:11:11 +0100 | [diff] [blame] | 20 |     def offlineDeployment = env.getEnvironment().containsKey("OFFLINE_DEPLOYMENT") && env["OFFLINE_DEPLOYMENT"].toBoolean() | 
| Mykyta Karpin | 1e4bfc9 | 2017-11-01 14:38:25 +0200 | [diff] [blame] | 21 |     def virtualenv_cmd = "virtualenv ${path} --python ${python}" | 
| Jakub Josef | 996f4ef | 2017-10-24 13:20:43 +0200 | [diff] [blame] | 22 |     if (useSystemPackages){ | 
 | 23 |         virtualenv_cmd += " --system-site-packages" | 
 | 24 |     } | 
| Tomáš Kukrál | 69c2545 | 2017-07-27 14:59:40 +0200 | [diff] [blame] | 25 |     if (clean) { | 
 | 26 |         common.infoMsg("Cleaning venv directory " + path) | 
 | 27 |         sh("rm -rf \"${path}\"") | 
 | 28 |     } | 
 | 29 |  | 
| Jakub Josef | 87a8a3c | 2018-01-26 12:11:11 +0100 | [diff] [blame] | 30 |     if(offlineDeployment){ | 
 | 31 |        virtualenv_cmd+=" --no-download" | 
 | 32 |     } | 
| Tomáš Kukrál | 69c2545 | 2017-07-27 14:59:40 +0200 | [diff] [blame] | 33 |     common.infoMsg("[Python ${path}] Setup ${python} environment") | 
| Sergey Kolekonov | ba20398 | 2016-12-21 18:32:17 +0400 | [diff] [blame] | 34 |     sh(returnStdout: true, script: virtualenv_cmd) | 
| Jakub Josef | 87a8a3c | 2018-01-26 12:11:11 +0100 | [diff] [blame] | 35 |     if(!offlineDeployment){ | 
| Jakub Josef | 5f97d53 | 2017-11-29 18:51:41 +0100 | [diff] [blame] | 36 |       try { | 
| Jakub Josef | a2491ad | 2018-01-15 16:26:27 +0100 | [diff] [blame] | 37 |           runVirtualenvCommand(path, "pip install -U setuptools pip") | 
| Jakub Josef | 5f97d53 | 2017-11-29 18:51:41 +0100 | [diff] [blame] | 38 |       } catch(Exception e) { | 
| Jakub Josef | a2491ad | 2018-01-15 16:26:27 +0100 | [diff] [blame] | 39 |           common.warningMsg("Setuptools and pip cannot be updated, you might be offline but OFFLINE_DEPLOYMENT global property not initialized!") | 
| Jakub Josef | 5f97d53 | 2017-11-29 18:51:41 +0100 | [diff] [blame] | 40 |       } | 
| Yuriy Taraday | 67352e9 | 2017-10-12 10:54:23 +0000 | [diff] [blame] | 41 |     } | 
| Vladislav Naumov | 1110386 | 2017-07-19 17:02:39 +0300 | [diff] [blame] | 42 |     if (reqs_path==null) { | 
 | 43 |         def args = "" | 
 | 44 |         for (req in reqs) { | 
 | 45 |             args = args + "${req}\n" | 
 | 46 |         } | 
 | 47 |         writeFile file: "${path}/requirements.txt", text: args | 
 | 48 |         reqs_path = "${path}/requirements.txt" | 
| Sergey Kolekonov | ba20398 | 2016-12-21 18:32:17 +0400 | [diff] [blame] | 49 |     } | 
| Jakub Josef | a2491ad | 2018-01-15 16:26:27 +0100 | [diff] [blame] | 50 |     runVirtualenvCommand(path, "pip install -r ${reqs_path}", true) | 
| Sergey Kolekonov | ba20398 | 2016-12-21 18:32:17 +0400 | [diff] [blame] | 51 | } | 
 | 52 |  | 
 | 53 | /** | 
 | 54 |  * Run command in specific python virtualenv | 
 | 55 |  * | 
 | 56 |  * @param path   Path to virtualenv | 
 | 57 |  * @param cmd    Command to be executed | 
| Jakub Josef | e2f4ebb | 2018-01-15 16:11:51 +0100 | [diff] [blame] | 58 |  * @param silent dont print any messages (optional, default false) | 
| Sergey Kolekonov | ba20398 | 2016-12-21 18:32:17 +0400 | [diff] [blame] | 59 |  */ | 
| Jakub Josef | e2f4ebb | 2018-01-15 16:11:51 +0100 | [diff] [blame] | 60 | def runVirtualenvCommand(path, cmd, silent=false) { | 
| Tomáš Kukrál | 69c2545 | 2017-07-27 14:59:40 +0200 | [diff] [blame] | 61 |     def common = new com.mirantis.mk.Common() | 
 | 62 |  | 
| Jakub Josef | 5feeee4 | 2018-01-08 15:50:36 +0100 | [diff] [blame] | 63 |     virtualenv_cmd = "set +x; . ${path}/bin/activate; ${cmd}" | 
| Jakub Josef | e2f4ebb | 2018-01-15 16:11:51 +0100 | [diff] [blame] | 64 |     if(!silent){ | 
 | 65 |         common.infoMsg("[Python ${path}] Run command ${cmd}") | 
 | 66 |     } | 
| Sergey Kolekonov | ba20398 | 2016-12-21 18:32:17 +0400 | [diff] [blame] | 67 |     output = sh( | 
 | 68 |         returnStdout: true, | 
 | 69 |         script: virtualenv_cmd | 
 | 70 |     ).trim() | 
 | 71 |     return output | 
 | 72 | } | 
 | 73 |  | 
| Ales Komarek | d874d48 | 2016-12-26 10:33:29 +0100 | [diff] [blame] | 74 |  | 
 | 75 | /** | 
 | 76 |  * Install docutils in isolated environment | 
 | 77 |  * | 
 | 78 |  * @param path        Path where virtualenv is created | 
 | 79 |  */ | 
 | 80 | def setupDocutilsVirtualenv(path) { | 
 | 81 |     requirements = [ | 
 | 82 |       'docutils', | 
 | 83 |     ] | 
 | 84 |     setupVirtualenv(path, 'python2', requirements) | 
 | 85 | } | 
 | 86 |  | 
 | 87 |  | 
| Sergey Kolekonov | ba20398 | 2016-12-21 18:32:17 +0400 | [diff] [blame] | 88 | @NonCPS | 
 | 89 | def loadJson(rawData) { | 
 | 90 |     return new groovy.json.JsonSlurperClassic().parseText(rawData) | 
 | 91 | } | 
 | 92 |  | 
 | 93 | /** | 
 | 94 |  * Parse content from markup-text tables to variables | 
 | 95 |  * | 
 | 96 |  * @param tableStr   String representing the table | 
 | 97 |  * @param mode       Either list (1st row are keys) or item (key, value rows) | 
 | 98 |  * @param format     Format of the table | 
 | 99 |  */ | 
| Ales Komarek | d874d48 | 2016-12-26 10:33:29 +0100 | [diff] [blame] | 100 | def parseTextTable(tableStr, type = 'item', format = 'rest', path = none) { | 
| Ales Komarek | 0e558ee | 2016-12-23 13:02:55 +0100 | [diff] [blame] | 101 |     parserFile = "${env.WORKSPACE}/textTableParser.py" | 
 | 102 |     parserScript = """import json | 
 | 103 | import argparse | 
 | 104 | from docutils.parsers.rst import tableparser | 
 | 105 | from docutils import statemachine | 
 | 106 |  | 
 | 107 | def parse_item_table(raw_data): | 
 | 108 |     i = 1 | 
 | 109 |     pretty_raw_data = [] | 
 | 110 |     for datum in raw_data: | 
 | 111 |         if datum != "": | 
 | 112 |             if datum[3] != ' ' and i > 4: | 
 | 113 |                 pretty_raw_data.append(raw_data[0]) | 
 | 114 |             if i == 3: | 
 | 115 |                 pretty_raw_data.append(datum.replace('-', '=')) | 
 | 116 |             else: | 
 | 117 |                 pretty_raw_data.append(datum) | 
 | 118 |             i += 1 | 
 | 119 |     parser = tableparser.GridTableParser() | 
 | 120 |     block = statemachine.StringList(pretty_raw_data) | 
 | 121 |     docutils_data = parser.parse(block) | 
 | 122 |     final_data = {} | 
 | 123 |     for line in docutils_data[2]: | 
 | 124 |         key = ' '.join(line[0][3]).strip() | 
 | 125 |         value = ' '.join(line[1][3]).strip() | 
 | 126 |         if key != "": | 
 | 127 |             try: | 
 | 128 |                 value = json.loads(value) | 
 | 129 |             except: | 
 | 130 |                 pass | 
 | 131 |             final_data[key] = value | 
 | 132 |         i+=1 | 
 | 133 |     return final_data | 
 | 134 |  | 
 | 135 | def parse_list_table(raw_data): | 
 | 136 |     i = 1 | 
 | 137 |     pretty_raw_data = [] | 
 | 138 |     for datum in raw_data: | 
 | 139 |         if datum != "": | 
 | 140 |             if datum[3] != ' ' and i > 4: | 
 | 141 |                 pretty_raw_data.append(raw_data[0]) | 
 | 142 |             if i == 3: | 
 | 143 |                 pretty_raw_data.append(datum.replace('-', '=')) | 
 | 144 |             else: | 
 | 145 |                 pretty_raw_data.append(datum) | 
 | 146 |             i += 1 | 
 | 147 |     parser = tableparser.GridTableParser() | 
 | 148 |     block = statemachine.StringList(pretty_raw_data) | 
 | 149 |     docutils_data = parser.parse(block) | 
 | 150 |     final_data = [] | 
 | 151 |     keys = [] | 
 | 152 |     for line in docutils_data[1]: | 
 | 153 |         for item in line: | 
 | 154 |              keys.append(' '.join(item[3]).strip()) | 
 | 155 |     for line in docutils_data[2]: | 
 | 156 |         final_line = {} | 
 | 157 |         key = ' '.join(line[0][3]).strip() | 
 | 158 |         value = ' '.join(line[1][3]).strip() | 
 | 159 |         if key != "": | 
 | 160 |             try: | 
 | 161 |                 value = json.loads(value) | 
 | 162 |             except: | 
 | 163 |                 pass | 
 | 164 |             final_data[key] = value | 
 | 165 |         i+=1 | 
 | 166 |     return final_data | 
 | 167 |  | 
 | 168 | def parse_list_table(raw_data): | 
 | 169 |     i = 1 | 
 | 170 |     pretty_raw_data = [] | 
 | 171 |     for datum in raw_data: | 
 | 172 |         if datum != "": | 
 | 173 |             if datum[3] != ' ' and i > 4: | 
 | 174 |                 pretty_raw_data.append(raw_data[0]) | 
 | 175 |             if i == 3: | 
 | 176 |                 pretty_raw_data.append(datum.replace('-', '=')) | 
 | 177 |             else: | 
 | 178 |                 pretty_raw_data.append(datum) | 
 | 179 |             i += 1 | 
 | 180 |     parser = tableparser.GridTableParser() | 
 | 181 |     block = statemachine.StringList(pretty_raw_data) | 
 | 182 |     docutils_data = parser.parse(block) | 
 | 183 |     final_data = [] | 
 | 184 |     keys = [] | 
 | 185 |     for line in docutils_data[1]: | 
 | 186 |         for item in line: | 
 | 187 |              keys.append(' '.join(item[3]).strip()) | 
 | 188 |     for line in docutils_data[2]: | 
 | 189 |         final_line = {} | 
 | 190 |         i = 0 | 
 | 191 |         for item in line: | 
 | 192 |             value = ' '.join(item[3]).strip() | 
 | 193 |             try: | 
 | 194 |                 value = json.loads(value) | 
 | 195 |             except: | 
 | 196 |                 pass | 
 | 197 |             final_line[keys[i]] = value | 
 | 198 |             i += 1 | 
 | 199 |         final_data.append(final_line) | 
 | 200 |     return final_data | 
 | 201 |  | 
 | 202 | def read_table_file(file): | 
 | 203 |     table_file = open(file, 'r') | 
| Ales Komarek | c000c15 | 2016-12-23 15:32:54 +0100 | [diff] [blame] | 204 |     raw_data = table_file.read().split('\\n') | 
| Ales Komarek | 0e558ee | 2016-12-23 13:02:55 +0100 | [diff] [blame] | 205 |     table_file.close() | 
 | 206 |     return raw_data | 
 | 207 |  | 
 | 208 | parser = argparse.ArgumentParser() | 
 | 209 | parser.add_argument('-f','--file', help='File with table data', required=True) | 
 | 210 | parser.add_argument('-t','--type', help='Type of table (list/item)', required=True) | 
 | 211 | args = vars(parser.parse_args()) | 
 | 212 |  | 
 | 213 | raw_data = read_table_file(args['file']) | 
 | 214 |  | 
 | 215 | if args['type'] == 'list': | 
 | 216 |   final_data = parse_list_table(raw_data) | 
 | 217 | else: | 
 | 218 |   final_data = parse_item_table(raw_data) | 
 | 219 |  | 
 | 220 | print json.dumps(final_data) | 
 | 221 | """ | 
 | 222 |     writeFile file: parserFile, text: parserScript | 
| Sergey Kolekonov | ba20398 | 2016-12-21 18:32:17 +0400 | [diff] [blame] | 223 |     tableFile = "${env.WORKSPACE}/prettytable.txt" | 
 | 224 |     writeFile file: tableFile, text: tableStr | 
| Ales Komarek | d874d48 | 2016-12-26 10:33:29 +0100 | [diff] [blame] | 225 |  | 
 | 226 |     cmd = "python ${parserFile} --file '${tableFile}' --type ${type}" | 
 | 227 |     if (path) { | 
| Ales Komarek | c6d28dd | 2016-12-28 12:59:38 +0100 | [diff] [blame] | 228 |         rawData = runVirtualenvCommand(path, cmd) | 
| Ales Komarek | d874d48 | 2016-12-26 10:33:29 +0100 | [diff] [blame] | 229 |     } | 
 | 230 |     else { | 
 | 231 |         rawData = sh ( | 
 | 232 |             script: cmd, | 
 | 233 |             returnStdout: true | 
 | 234 |         ).trim() | 
 | 235 |     } | 
| Sergey Kolekonov | ba20398 | 2016-12-21 18:32:17 +0400 | [diff] [blame] | 236 |     data = loadJson(rawData) | 
 | 237 |     echo("[Parsed table] ${data}") | 
 | 238 |     return data | 
 | 239 | } | 
 | 240 |  | 
 | 241 | /** | 
 | 242 |  * Install cookiecutter in isolated environment | 
 | 243 |  * | 
 | 244 |  * @param path        Path where virtualenv is created | 
 | 245 |  */ | 
 | 246 | def setupCookiecutterVirtualenv(path) { | 
 | 247 |     requirements = [ | 
 | 248 |         'cookiecutter', | 
| Jakub Josef | 4df7827 | 2017-04-26 14:36:36 +0200 | [diff] [blame] | 249 |         'jinja2==2.8.1', | 
 | 250 |         'PyYAML==3.12' | 
| Sergey Kolekonov | ba20398 | 2016-12-21 18:32:17 +0400 | [diff] [blame] | 251 |     ] | 
 | 252 |     setupVirtualenv(path, 'python2', requirements) | 
 | 253 | } | 
 | 254 |  | 
 | 255 | /** | 
 | 256 |  * Generate the cookiecutter templates with given context | 
 | 257 |  * | 
| Jakub Josef | 4e10c37 | 2017-04-26 14:13:50 +0200 | [diff] [blame] | 258 |  * @param template template | 
 | 259 |  * @param context template context | 
 | 260 |  * @param path Path where virtualenv is created (optional) | 
 | 261 |  * @param templatePath path to cookiecutter template repo (optional) | 
| Sergey Kolekonov | ba20398 | 2016-12-21 18:32:17 +0400 | [diff] [blame] | 262 |  */ | 
| Jakub Josef | 4e10c37 | 2017-04-26 14:13:50 +0200 | [diff] [blame] | 263 | def buildCookiecutterTemplate(template, context, outputDir = '.', path = null, templatePath = ".") { | 
| Tomáš Kukrál | dad7b46 | 2017-03-27 13:53:05 +0200 | [diff] [blame] | 264 |     configFile = "default_config.yaml" | 
 | 265 |     configString = "default_context:\n" | 
| Tomáš Kukrál | 6de8504 | 2017-04-12 17:49:05 +0200 | [diff] [blame] | 266 |     writeFile file: configFile, text: context | 
| Jakub Josef | 4e61cc0 | 2017-04-26 14:29:09 +0200 | [diff] [blame] | 267 |     command = ". ${path}/bin/activate; if [ -f ${templatePath}/generate.py ]; then python ${templatePath}/generate.py --config-file ${configFile} --template ${template} --output-dir ${outputDir}; else cookiecutter --config-file ${configFile} --output-dir ${outputDir} --overwrite-if-exists --verbose --no-input ${template}; fi" | 
| Sergey Kolekonov | ba20398 | 2016-12-21 18:32:17 +0400 | [diff] [blame] | 268 |     output = sh (returnStdout: true, script: command) | 
 | 269 |     echo("[Cookiecutter build] Output: ${output}") | 
 | 270 | } | 
 | 271 |  | 
 | 272 | /** | 
 | 273 |  * Install jinja rendering in isolated environment | 
 | 274 |  * | 
 | 275 |  * @param path        Path where virtualenv is created | 
 | 276 |  */ | 
 | 277 | def setupJinjaVirtualenv(path) { | 
 | 278 |     requirements = [ | 
 | 279 |       'jinja2-cli', | 
 | 280 |       'pyyaml', | 
 | 281 |     ] | 
 | 282 |     setupVirtualenv(path, 'python2', requirements) | 
 | 283 | } | 
 | 284 |  | 
 | 285 | /** | 
 | 286 |  * Generate the Jinja templates with given context | 
 | 287 |  * | 
 | 288 |  * @param path        Path where virtualenv is created | 
 | 289 |  */ | 
 | 290 | def jinjaBuildTemplate (template, context, path = none) { | 
 | 291 |     contextFile = "jinja_context.yml" | 
 | 292 |     contextString = "" | 
 | 293 |     for (parameter in context) { | 
 | 294 |         contextString = "${contextString}${parameter.key}: ${parameter.value}\n" | 
 | 295 |     } | 
 | 296 |     writeFile file: contextFile, text: contextString | 
 | 297 |     cmd = "jinja2 ${template} ${contextFile} --format=yaml" | 
 | 298 |     data = sh (returnStdout: true, script: cmd) | 
 | 299 |     echo(data) | 
 | 300 |     return data | 
 | 301 | } | 
| Oleg Grigorov | bec4558 | 2017-09-12 20:29:24 +0300 | [diff] [blame] | 302 |  | 
 | 303 | /** | 
 | 304 |  * Install salt-pepper in isolated environment | 
 | 305 |  * | 
 | 306 |  * @param path        Path where virtualenv is created | 
| chnyda | a0dbb25 | 2017-10-05 10:46:09 +0200 | [diff] [blame] | 307 |  * @param url         SALT_MASTER_URL | 
 | 308 |  * @param credentialsId        Credentials to salt api | 
| Oleg Grigorov | bec4558 | 2017-09-12 20:29:24 +0300 | [diff] [blame] | 309 |  */ | 
| Mykyta Karpin | 9491623 | 2017-11-15 10:20:59 +0200 | [diff] [blame] | 310 | def setupPepperVirtualenv(path, url, credentialsId, clean = false) { | 
| chnyda | a0dbb25 | 2017-10-05 10:46:09 +0200 | [diff] [blame] | 311 |     def common = new com.mirantis.mk.Common() | 
 | 312 |  | 
 | 313 |     // virtualenv setup | 
| Jakub Josef | 66284f5 | 2018-01-29 18:13:01 +0100 | [diff] [blame] | 314 |     requirements = ['salt-pepper==0.5.*'] | 
| Mykyta Karpin | 9491623 | 2017-11-15 10:20:59 +0200 | [diff] [blame] | 315 |     setupVirtualenv(path, 'python2', requirements, null, clean, true) | 
| chnyda | bcfff18 | 2017-11-29 10:24:36 +0100 | [diff] [blame] | 316 |  | 
| chnyda | a0dbb25 | 2017-10-05 10:46:09 +0200 | [diff] [blame] | 317 |     // pepperrc creation | 
 | 318 |     rcFile = "${path}/pepperrc" | 
 | 319 |     creds = common.getPasswordCredentials(credentialsId) | 
 | 320 |     rc = """\ | 
 | 321 | [main] | 
 | 322 | SALTAPI_EAUTH=pam | 
 | 323 | SALTAPI_URL=${url} | 
 | 324 | SALTAPI_USER=${creds.username} | 
 | 325 | SALTAPI_PASS=${creds.password.toString()} | 
 | 326 | """ | 
 | 327 |     writeFile file: rcFile, text: rc | 
 | 328 |     return rcFile | 
| Jakub Josef | d067f61 | 2017-09-26 13:42:56 +0200 | [diff] [blame] | 329 | } | 
| Oleh Hryhorov | 44569fb | 2017-10-26 17:04:55 +0300 | [diff] [blame] | 330 |  | 
 | 331 | /** | 
 | 332 |  * Install devops in isolated environment | 
 | 333 |  * | 
 | 334 |  * @param path        Path where virtualenv is created | 
 | 335 |  * @param clean       Define to true is the venv have to cleaned up before install a new one | 
 | 336 |  */ | 
 | 337 | def setupDevOpsVenv(venv, clean=false) { | 
 | 338 |     requirements = ['git+https://github.com/openstack/fuel-devops.git'] | 
 | 339 |     setupVirtualenv(venv, 'python2', requirements, null, false, clean) | 
 | 340 | } |