utils.cdk
cdk
Common code useful for a CDK diff/deploy.
Example Usage: from utils import cdk from utils.cdk import get_cdk_required_version
1#!/usr/bin/env python3 2""" 3cdk 4 5Common code useful for a CDK diff/deploy. 6 7Example Usage: 8 from utils import cdk 9 from utils.cdk import get_cdk_required_version 10""" 11 12import subprocess 13import os 14from pathlib import Path 15import re 16import typing 17import glob 18import json 19import yaml 20 21try: 22 import loggy 23except ImportError: 24 from utils import loggy 25try: 26 from .common import subprocess_long as _long_run, ChDir as _chdir 27except ImportError: 28 from utils.common import subprocess_long as _long_run, ChDir as _chdir 29 30 31 32 33def deploy(properties_env: str, lang: typing.Optional[str] = None, path: typing.Optional[str] = None) -> bool: 34 """ 35 deploy() 36 37 Runs `cdk deploy` using the properties_env to locate the `properties.env.json` file 38 39 properties_env: String containing env name to locate the properties file 40 lang: String representing the type of CDK code to deploy 41 path: String (Optional) Will search for the cdk.json file recursively if this path is not set. 42 43 Returns: True/False 44 """ 45 46 _CDK_PATH = path if path is not None else os.path.dirname(glob.glob('**/cdk.json', recursive=True)[0]) 47 _PROPS_FILE = f"properties.{properties_env}.json" 48 _CDK_DEPLOY_FILE = f"{os.getcwd()}/cdk.deploy.txt" 49 50 with _chdir(_CDK_PATH): 51 env_file = Path(_PROPS_FILE).read_text() 52 # env_data = json.load(env_file) 53 loggy.info(f"cdk.deploy(): {env_file}") 54 55 if not install_cdk_requirements(cdk_lang=lang): 56 loggy.info("cdk.deploy(): Failed to install cdk requirements. Check logs.") 57 return False 58 59 my_env = os.environ.copy() 60 my_env["ENVIRONMENT"] = properties_env 61 62 _process_output = _long_run(['cdk', 'deploy', '--require-approval', 'never', '--all'], env=my_env, check=False) 63 loggy.info("----------------------------------") 64 loggy.info(f"cdk.deploy(): CDK returned {str(_process_output.returncode)}") 65 66 with open(_CDK_DEPLOY_FILE, 'w') as file: 67 if _process_output.stderr: 68 file.write(_process_output.stderr) 69 70 if _process_output.stdout: 71 file.write(_process_output.stdout) 72 73 if _process_output.returncode != 0: 74 return False 75 76 return True 77 78 79def diff(properties_env: str, lang: typing.Optional[str] = None, path: typing.Optional[str] = None) -> bool: 80 """ 81 diff() 82 83 Runs `cdk diff` using the properties_env to locate the `properties.env.json` file 84 85 properties_env: String containing env name to locate the properties file 86 lang: String representing the type of CDK code to deploy (python, ts, typescript) 87 path: String (Optional) Will search for the cdk.json file recursively if this path is not set. 88 89 Returns: True/False 90 """ 91 92 _CDK_PATH = path if path is not None else os.path.dirname(glob.glob('**/cdk.json', recursive=True)[0]) 93 _PROPS_FILE = f"properties.{properties_env}.json" 94 _CDK_DIFF_FILE = f"{os.getcwd()}/cdk.diff.txt" 95 96 _EXIT = True 97 with _chdir(_CDK_PATH): 98 env_file = Path(_PROPS_FILE).read_text() 99 # env_data = json.load(env_file) 100 loggy.info(f"cdk.diff(): {env_file}") 101 102 if not install_cdk_requirements(cdk_lang=lang): 103 loggy.info("cdk.diff(): Failed to install cdk requirements. Check logs.") 104 return False 105 106 my_env = os.environ.copy() 107 my_env["ENVIRONMENT"] = properties_env 108 109 _process_output = _long_run(['cdk', 'diff', '--fail', '--verbose'], env=my_env, check=False) 110 loggy.info("----------------------------------") 111 loggy.info(f"cdk.diff(): CDK returned {str(_process_output.returncode)}") 112 # loggy.info("----------------------------------") 113 # loggy.info(f"cdk.diff(): CDK returned {str(_process_output.stderr)}") 114 # loggy.info("----------------------------------") 115 # loggy.info(f"cdk.diff(): CDK returned {str(_process_output.stdout)}") 116 # loggy.info("----------------------------------") 117 118 if _process_output.returncode != 0: 119 loggy.info("cdk.diff(): Testing for CDK Diff or Error.") 120 121 with open(_CDK_DIFF_FILE, 'w') as file: 122 file.write(_process_output.stderr) 123 124 if '[~]' in _process_output.stderr or '[+]' in _process_output.stderr or '[-]' in _process_output.stderr or '[=]' in _process_output.stderr: 125 loggy.info("cdk.diff(): CDK Diff found!") 126 _EXIT = True 127 else: 128 loggy.info("cdk.diff(): CDK ERROR!") 129 for stack in glob.glob('cdk.out/*.json'): 130 loggy.info("----------------------------------") 131 loggy.info("STACK: " + stack) 132 _stack_yaml = yaml.dump(json.loads(Path(stack).read_text())) 133 loggy.info(_stack_yaml) 134 _EXIT = False 135 136 else: 137 # if 'DEPLOY_OVERRIDE' in os.environ.keys(): 138 # print("cdkDiff.__main__(): No CDK diff! Overriding CDK_DIFF because DEPLOY_OVERRIDE") 139 # _EXIT = 0 140 # else: 141 with open(_CDK_DIFF_FILE, 'w') as file: 142 file.write("NO CDK diff found. This could be an AMI SSM Param change. Deploy proceding.") 143 144 loggy.info("cdk.diff(): NO CDK diff found. This could be an AMI SSM Param change. Deploy proceding.") 145 _EXIT = True 146 147 return _EXIT 148 149 150def diff_pretty(diff_file: str='cdk.diff.txt', output_file: str='cdk.diff.html', verbose: bool=False) -> bool: 151 152 _CDK_DIFF_FILE = diff_file 153 _CDK_HTML_OUTPUT_FILE = output_file 154 _VERBOSE = verbose 155 156 html_template = """ 157<!DOCTYPE html> 158<html> 159 <head> 160 <meta charset="utf-8" /> 161 <title>prettyplan</title> 162 <meta name="viewport" content="width=device-width, initial-scale=1" /> 163 <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/diff2html/bundles/css/diff2html.min.css" /> 164 <style> 165 body { 166 font-family: Arial, Helvetica, sans-serif; 167 text-rendering: optimizeLegibility; 168 background: #ecf7fe; 169 color: #000000c0; 170 font-size: 15px; 171 margin: 0; 172 } 173 @keyframes fade-in { 174 0% { 175 opacity: 0; 176 } 177 100% { 178 opacity: 1; 179 } 180 } 181 .stripe { 182 width: 100%; 183 height: 5px; 184 background: #5c4ce4; 185 animation-name: wipe-in; 186 animation-duration: 1s; 187 } 188 @keyframes wipe-in { 189 0% { 190 width: 0%; 191 } 192 100% { 193 width: 100%; 194 } 195 } 196 #release-notification { 197 background: #5c4ce4; 198 color: white; 199 font-weight: bold; 200 text-align: center; 201 overflow: hidden; 202 padding: 10px 0 15px 0; 203 height: 20px; 204 animation-name: notification-pop-in; 205 animation-duration: 2s; 206 } 207 #release-notification a { 208 color: white; 209 } 210 #release-notification.dismissed { 211 animation-name: notification-pop-out; 212 animation-duration: 0.5s; 213 height: 0; 214 padding: 0; 215 } 216 @keyframes notification-pop-in { 217 0% { 218 height: 0; 219 padding: 0; 220 } 221 50% { 222 height: 0; 223 padding: 0; 224 } 225 } 226 @keyframes notification-pop-out { 227 0% { 228 height: 20px; 229 padding: 10px 0 15px 0; 230 } 231 100% { 232 height: 0; 233 padding: 0; 234 } 235 } 236 #modal-container { 237 animation-name: fade-in; 238 animation-duration: 0.2s; 239 } 240 .modal-pane { 241 position: fixed; 242 top: 0; 243 left: 0; 244 width: 100%; 245 height: 100%; 246 background: #ffffffe6; 247 z-index: 10; 248 } 249 .modal-content { 250 position: fixed; 251 width: 60%; 252 height: 60%; 253 top: 50%; 254 left: 50%; 255 transform: translate(-50%, -50%); 256 background: #ffffff; 257 box-shadow: 0 2px 6px 0 hsla(0, 0%, 0%, 0.2); 258 z-index: 20; 259 } 260 .modal-close { 261 position: absolute; 262 right: 0; 263 padding: 10px; 264 } 265 .modal-close button.text-button { 266 color: #4526ac; 267 text-decoration: none; 268 font-weight: normal; 269 } 270 .release-notes { 271 max-width: 80%; 272 margin: 0 auto 0 auto; 273 overflow-y: auto; 274 max-height: 100%; 275 } 276 #branding { 277 float: right; 278 padding-top: 10px; 279 padding-right: 10px; 280 font-size: 10px; 281 color: #4526ac; 282 text-align: right; 283 } 284 #branding a { 285 color: #4526ac; 286 } 287 .container { 288 margin: 10px 10px 0 10px; 289 animation-name: fade-in; 290 animation-duration: 1s; 291 } 292 @media only screen and (min-width: 600px) { 293 .container { 294 max-width: 80%; 295 margin-left: auto; 296 margin-right: auto; 297 } 298 } 299 h1, 300 h2 { 301 text-align: center; 302 color: #4526ac; 303 } 304 #terraform-plan { 305 width: 100%; 306 min-height: 300px; 307 border: none; 308 box-shadow: 0 2px 6px 0 hsla(0, 0%, 0%, 0.2); 309 padding: 10px; 310 margin-bottom: 10px; 311 resize: none; 312 background: #ffffffe6; 313 } 314 button { 315 font-size: 18px; 316 background: #5c4ce4; 317 color: #fff; 318 box-shadow: 0 2px 6px 0 hsla(0, 0%, 0%, 0.2); 319 border: none; 320 border-radius: 2px; 321 min-width: 170px; 322 height: 40px; 323 } 324 button:hover { 325 background: #6567ea; 326 cursor: pointer; 327 } 328 button:active { 329 background: #5037ca; 330 } 331 button.text-button { 332 background: none; 333 box-shadow: none; 334 border-radius: 0; 335 width: auto; 336 height: auto; 337 text-decoration: underline; 338 font-size: inherit; 339 font-weight: inherit; 340 font-family: Arial, Helvetica, sans-serif; 341 color: inherit; 342 text-align: inherit; 343 padding: 0; 344 } 345 #parsing-error-message { 346 background-color: #ffffff; 347 padding: 10px; 348 color: #000000c0; 349 margin: 4px; 350 box-shadow: 0 2px 6px 0 hsla(0, 0%, 0%, 0.2); 351 font-weight: bold; 352 border-left: 2px solid red; 353 animation-name: error; 354 animation-duration: 1s; 355 } 356 @keyframes error { 357 0% { 358 background-color: red; 359 } 360 100% { 361 background-color: white; 362 } 363 } 364 .prettyplan ul { 365 padding-left: 0; 366 font-size: 13px; 367 } 368 .prettyplan li { 369 list-style: none; 370 background: #ffffffe6; 371 padding: 10px; 372 color: #000000c0; 373 margin: 4px; 374 box-shadow: 0 2px 6px 0 hsla(0, 0%, 0%, 0.2); 375 } 376 .prettyplan ul.warnings li { 377 border-left: 3px solid #757575; 378 } 379 .prettyplan ul.actions li.update { 380 border-left: 3px solid #ff8f00; 381 } 382 .prettyplan ul.actions li.create { 383 border-left: 3px solid #2e7d32; 384 } 385 .prettyplan ul.actions li.addition { 386 border-left: 3px solid #2e7d32; 387 } 388 .prettyplan ul.actions li.destroy { 389 border-left: 3px solid #b71c1c; 390 } 391 .prettyplan ul.actions li.removal { 392 border-left: 3px solid #b71c1c; 393 } 394 .prettyplan ul.actions li.recreate { 395 border-left: 3px solid #1565c0; 396 } 397 .prettyplan ul.actions li.read { 398 border-left: 3px solid #519bf0; 399 } 400 .badge { 401 display: inline-block; 402 text-transform: uppercase; 403 margin-right: 10px; 404 padding: 3px; 405 font-size: 12px; 406 font-weight: bold; 407 } 408 .warnings .badge { 409 color: #757575; 410 } 411 li.update .badge { 412 color: #ff8f00; 413 } 414 li.create .badge { 415 color: #2e7d32; 416 } 417 li.addition .badge { 418 color: #2e7d32; 419 } 420 li.destroy .badge { 421 color: #b71c1c; 422 } 423 li.removal .badge { 424 color: #b71c1c; 425 } 426 li.recreate .badge { 427 color: #1565c0; 428 } 429 li.read .badge { 430 color: #519bf0; 431 } 432 .id-segment:not(:last-child)::after { 433 content: ' > '; 434 } 435 .id-segment.name, 436 .id-segment.type { 437 font-weight: bold; 438 } 439 .change-count { 440 float: right; 441 } 442 .summary { 443 cursor: pointer; 444 } 445 .no-diff-changes-breakdown { 446 margin: 5px auto 0 auto; 447 padding: 5px; 448 } 449 .no-diff-changes-breakdown table { 450 width: 100%; 451 word-break: break-all; 452 font-size: 13px; 453 } 454 .no-diff-changes-breakdown table td { 455 padding: 10px; 456 width: 40%; 457 } 458 pre { 459 white-space: pre-wrap; 460 background: #f3f3f3; 461 } 462 .no-diff-changes-breakdown table td.property { 463 width: 20%; 464 text-align: right; 465 font-weight: bold; 466 } 467 .no-diff-changes-breakdown table tr:nth-child(even) { 468 background-color: #f5f5f5; 469 } 470 .forces-new-resource { 471 color: #b71c1c; 472 } 473 .collapsed, 474 .hidden { 475 display: none; 476 } 477 .actions button { 478 background: none; 479 border: none; 480 text-decoration: underline; 481 color: black; 482 box-shadow: none; 483 font-weight: bold; 484 font-size: 14px; 485 } 486 .d2h-icon { 487 display: none; 488 } 489 </style> 490 </head> 491 <body> 492 <div class="stripe"></div> 493 <div class="container"> 494 <h1>prettyplan</h1> 495 <div id="parsing-error-message" class="hidden"> 496 That doesn't look like a Terraform plan. Did you copy the entire output (without colouring) from the plan 497 command? 498 </div> 499 <div id="prettyplan" class="prettyplan"> 500 <ul id="errors" class="errors"></ul> 501 <ul id="warnings" class="warnings"></ul> 502 <button class="expand-all" onclick="expandAll()">Expand all</button> 503 <button class="collapse-all hidden" onclick="collapseAll()">Collapse all</button> 504 <div id="stacks"></div> 505 <ul id="actions" class="actions"></ul> 506 <pre id="diff"></pre> 507 </div> 508 </div> 509 <script> 510 function accordion(element) { 511 const changes = element.parentElement.getElementsByClassName('changes'); 512 for (var i = 0; i < changes.length; i++) { 513 toggleClass(changes[i], 'collapsed'); 514 } 515 } 516 function toggleClass(element, className) { 517 if (!element.className.match(className)) { 518 element.className += ' ' + className; 519 } else { 520 element.className = element.className.replace(className, ''); 521 } 522 } 523 function addClass(element, className) { 524 if (!element.className.match(className)) element.className += ' ' + className; 525 } 526 function removeClass(element, className) { 527 element.className = element.className.replace(className, ''); 528 } 529 function expandAll() { 530 const sections = document.querySelectorAll('.changes.collapsed'); 531 for (var i = 0; i < sections.length; i++) { 532 toggleClass(sections[i], 'collapsed'); 533 } 534 toggleClass(document.querySelector('.expand-all'), 'hidden'); 535 toggleClass(document.querySelector('.collapse-all'), 'hidden'); 536 } 537 function collapseAll() { 538 const sections = document.querySelectorAll('.changes:not(.collapsed)'); 539 for (var i = 0; i < sections.length; i++) { 540 toggleClass(sections[i], 'collapsed'); 541 } 542 toggleClass(document.querySelector('.expand-all'), 'hidden'); 543 toggleClass(document.querySelector('.collapse-all'), 'hidden'); 544 } 545 function removeChildren(element) { 546 while (element.lastChild) { 547 element.removeChild(element.lastChild); 548 } 549 } 550 function createModalContainer() { 551 const modalElement = document.createElement('div'); 552 modalElement.id = 'modal-container'; 553 document.body.appendChild(modalElement); 554 return modalElement; 555 } 556 function closeModal() { 557 const modalElement = document.getElementById('modal-container'); 558 document.body.removeChild(modalElement); 559 } 560 </script> 561 </body> 562</html> 563""" 564 565 if _VERBOSE: 566 loggy.info("diff_pretty()): BEGIN") 567 568 if not Path(_CDK_DIFF_FILE).exists(): 569 loggy.info(f"diff_pretty(): ERROR. INPUT FILE ({_CDK_DIFF_FILE}) does NOT exist.") 570 return False 571 572 if _VERBOSE: 573 loggy.info(f"diff_pretty(): Reading input file: {_CDK_DIFF_FILE}") 574 575 cdk_out = Path(_CDK_DIFF_FILE).read_text() 576 577 if _VERBOSE: 578 loggy.info(f"diff_pretty(): {cdk_out}") 579 580 # 581 # Here, we split the stack output into an array based on `Stack xyz` lines 582 # 583 stacks = [f"Stack {e}" for e in cdk_out.split('Stack ') if e] 584 585 # 586 # This is a Hack to get rid of any warnings at the top of the output before 587 # we encounter our first `Stack xyz` line. 588 # 589 for idx, l in enumerate(stacks): 590 match = False 591 for hack_line in cdk_out.split('\n'): 592 if l.split('\n')[0] in hack_line: 593 match = True 594 break 595 596 if not match: 597 del stacks[idx] 598 continue 599 600 # 601 # Convert each `Stack xyz` line to our HTML format 602 # 603 for idx, l in enumerate(stacks): 604 new_line = [] 605 diff_type = "" 606 divs_open = 0 607 ul_li_open = 0 608 for ndx, n in enumerate(l.split('\n')): 609 if _VERBOSE: 610 loggy.info(f"diff_pretty(): DEBUG: ({str(idx)})-({str(ndx)}) - {str(n)}") 611 612 if ndx == 0: 613 # new_line.append(f"<div class=\"stack\"><h2>{n}</h2><div class=\"raw-diff\"><button onclick=\"accordion(this)\">Expand this Stack</button><div class=\"changes\">") 614 615 # 616 # 2 open divs 617 # 618 divs_open += 2 619 new_line.append(f"<div class=\"stack\"><h2>{n}</h2><button onclick=\"accordion(this)\">Expand this Stack</button><div class=\"changes\">") 620 else: 621 if n.startswith('IAM Policy Changes'): 622 diff_type = "IAM" 623 elif n.startswith('Resources'): 624 diff_type = "Resources" 625 elif n.startswith('Outputs'): 626 diff_type = "Outputs" 627 elif n.startswith('There were no differences'): 628 diff_type = "NoDiff" 629 630 if 'Resources' in diff_type or 'Outputs' in diff_type: 631 if n.startswith('[+]'): 632 if ul_li_open > 0: 633 ul_li_open = 0 634 divs_open -= 1 635 new_line.append('<pre></div></li></ul>') 636 637 ul_li_open += 1 638 divs_open += 1 639 new_line.append(f"<ul class=\"actions\"><li class=\"create\"><div class=\"summary\" onclick=\"accordion(this)\"><span class=\"badge\">Addition</span></div><div class\"changes\"><pre>{n}") 640 elif n.startswith('[-]'): 641 if ul_li_open > 0: 642 ul_li_open = 0 643 divs_open -= 1 644 new_line.append('</pre></div></li></ul>') 645 646 ul_li_open += 1 647 divs_open += 1 648 new_line.append(f"<ul class=\"actions\"><li class=\"destroy\"><div class=\"summary\" onclick=\"accordion(this)\"><span class=\"badge\">Delete</span></div><div class\"changes\"><pre>{n}") 649 elif n.startswith('[~]'): 650 if ul_li_open > 0: 651 ul_li_open = 0 652 divs_open -= 1 653 new_line.append('</pre></div></li></ul>') 654 655 ul_li_open += 1 656 divs_open += 1 657 if 'replace' in n or ((len(l)-1) >= ndx+1 and 'replace' in l[ndx+1]): 658 new_line.append(f"<ul class=\"actions\"><li class=\"destroy\"><div class=\"summary\" onclick=\"accordion(this)\"><span class=\"badge\">REPLACEMENT</span></div><div class\"changes\"><pre>{n}") 659 else: 660 new_line.append(f"<ul class=\"actions\"><li class=\"update\"><div class=\"summary\" onclick=\"accordion(this)\"><span class=\"badge\">Update</span></div><div class\"changes\"><pre>{n}") 661 662 else: 663 new_line.append(f"{n}") 664 665 elif 'IAM' in diff_type: 666 if divs_open <= 2: 667 ul_li_open += 1 668 divs_open += 1 669 new_line.append(f"<ul class=\"actions\"><li class=\"update\"><div class=\"summary\" onclick=\"accordion(this)\"><span class=\"badge\">Update</span></div><div class\"changes\"><pre>{n}") 670 else: 671 new_line.append(f"{n}") 672 673 elif 'NoDiff' in diff_type: 674 if divs_open <= 2: 675 ul_li_open += 1 676 divs_open += 1 677 new_line.append(f"<ul class=\"actions\"><li class=\"read\"><div class=\"summary\" onclick=\"accordion(this)\"><span class=\"badge\">No Diff</span></div><div class\"changes\"><pre>{n}") 678 else: 679 new_line.append(f"{n}") 680 681 while ul_li_open > 0: 682 ul_li_open -= 1 683 divs_open -= 1 684 new_line.append('</pre></div></li></ul>') 685 686 while divs_open > 0: 687 divs_open -= 1 688 new_line.append('</div>') 689 690 stacks[idx] = '\n'.join(new_line) 691 692 # 693 # If there are no stacks, then we can print this as a giant error 694 # 695 if not stacks: 696 print("cdk-diff-pretty.__main__(): ERROR: No stacks found. Outputting diff as ERROR.") 697 stacks.append("<div class=\"stack\"><h2>CDK DIFF ERROR</h2><button onclick=\"accordion(this)\">Expand this Stack</button><div class=\"changes\">") 698 stacks.append("<ul class=\"actions\"><li class=\"destroy\"><div class=\"summary\" onclick=\"accordion(this)\"><span class=\"badge\">CDK DIFF ERROR</span></div><div class\"changes\"><pre>") 699 stacks.append(f"{cdk_out}") 700 stacks.append('</pre></div></li></ul>') 701 stacks.append('</div></div>') 702 703 html_template = html_template.replace('<h1>prettyplan</h1>', '<h1>CDK Diff</h1>') 704 html_template = html_template.replace('<title>prettyplan</title>', '<title>CDK Diff</title>') 705 706 html_template = html_template.replace('<div id="stacks"></div>', f"<div id=\"stacks\">{' '.join(stacks)}</div>") 707 708 if _VERBOSE: 709 loggy.info(f"diff_pretty(): Writing output file to {_CDK_HTML_OUTPUT_FILE}") 710 711 Path(_CDK_HTML_OUTPUT_FILE).write_text(html_template) 712 if _VERBOSE: 713 loggy.info("diff_pretty(): END") 714 715 return True 716 717 718def get_cdk_installed_version() -> str: 719 """ 720 get_cdk_installed_version() 721 722 Get the CDK installed version. 723 724 Returns: String representing the installed version 725 """ 726 727 loggy.info("cdk.get_cdk_installed_version(): BEGIN") 728 process_output = subprocess.run(['cdk', '--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True) 729 loggy.info("cdk.get_cdk_installed_version(): BEGIN") 730 return process_output.stdout.decode().split(' ')[0] 731 732 733def get_cdk_required_version() -> str: 734 """ 735 get_cdk_required_version() 736 737 Get the CDK required version. If `cdk_lock_version` file exists, pulls the 738 version value from there. Otherwise, returns the installed version of CDK. 739 740 Returns: String representing the required version 741 """ 742 loggy.info("cdk.get_cdk_required_version(): BEGIN") 743 if os.path.exists('cdk_lock_version'): 744 _CDK_REQUIRED_VERSION = Path('cdk_lock_version').read_text().strip() 745 else: 746 _CDK_REQUIRED_VERSION = get_cdk_installed_version() 747 748 loggy.info("cdk.get_cdk_required_version(): END") 749 return _CDK_REQUIRED_VERSION 750 751 752def set_cdk_installed_version() -> str: 753 """ 754 set_cdk_installed_version() 755 756 Set the CDK installed version. If the required version of CDK is different 757 from the installed version, run the npm commands to install the correct 758 version. 759 760 Returns: String representing the required version 761 """ 762 763 loggy.info("cdk.set_cdk_installed_version(): BEGIN") 764 _CDK_REQUIRED_VERSION = get_cdk_required_version() 765 _CDK_INSTALLED_VERSION = get_cdk_installed_version() 766 loggy.info("cdk.set_cdk_installed_version(): _CDK_REQUIRED_VERSION: " + _CDK_REQUIRED_VERSION) 767 loggy.info("cdk.set_cdk_installed_version(): _CDK_INSTALLED_VERSION: " + _CDK_INSTALLED_VERSION) 768 769 if _CDK_REQUIRED_VERSION != _CDK_INSTALLED_VERSION: 770 subprocess.run(['sudo', 'npm', 'install', '-g', 'aws-cdk@' + _CDK_REQUIRED_VERSION], stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True) 771 772 loggy.info("cdk.set_cdk_installed_version(): END") 773 return _CDK_REQUIRED_VERSION 774 775 776def install_cdk_requirements(cdk_lang: str) -> bool: 777 """ 778 install_cdk_requirements() 779 780 Install requirements for the specific type/language of cdk deployment. 781 782 cdk_lang: String representing the type of CDK code to deploy 783 784 Returns: True/False 785 """ 786 loggy.info("cdk.install_cdk_requirements(): BEGIN") 787 _cdk_required_version = set_cdk_installed_version() 788 if cdk_lang == 'python': 789 loggy.info("cdk.install_cdk_requirements(): Installing python requirements.") 790 791 if os.path.exists('setup.py'): 792 loggy.info("cdk.install_cdk_requirements(): Original setup.py file.") 793 _file_contents = Path('setup.py').read_text() 794 loggy.info(_file_contents) 795 796 # 797 # TAW 20220529 - Setting required version changes between cdk v1 and v2 798 # 799 if _cdk_required_version.startswith('1'): 800 loggy.info("cdk.install_cdk_requirements(): Detected cdk v1. Adding version number to aws_cdk.aws_*.") 801 _file_contents = re.sub('aws_cdk.aws_(.*)"', r'aws_cdk.aws_\1=='+_cdk_required_version+'"', _file_contents) 802 elif _cdk_required_version.startswith('2'): 803 loggy.info("cdk.install_cdk_requirements(): Detected cdk v2. Adding version number to aws-cdk-lib*.") 804 _file_contents = re.sub('aws-cdk-lib(.*)"', r'aws-cdk-lib\1=='+_cdk_required_version+'"', _file_contents) 805 else: 806 loggy.info("cdk.install_cdk_requirements(): Detected unknown cdk version. You might need to modify cdk.py in gocd library to support this.") 807 808 with open('setup.py', 'w') as file: 809 file.write(_file_contents) 810 811 # subprocess.run(['sed', '-i', '-E', '\'s|aws_cdk.aws_(.*)"|aws_cdk.aws_\1=='+_cdk_required_version+'"|g\'', 'setup.py'], check=True) 812 # sed -i -E 's|aws_cdk.aws_(.*)"|aws_cdk.aws_\1=='$_CDK_REQUIRED_VERSION'"|g' setup.py 813 814 loggy.info("cdk.install_cdk_requirements(): Modified setup.py file.") 815 _new_file_contents = Path('setup.py').read_text() 816 loggy.info(_new_file_contents) 817 818 subprocess.run(['pip3', 'install', '-r', 'requirements.txt'], check=True) 819 else: 820 loggy.info("cdk.install_cdk_requirements(): Error. setup.py does not exist.") 821 elif cdk_lang == 'ts' or cdk_lang == 'typescript': 822 loggy.info("cdk.install_cdk_requirements(): Installing npm requirements.") 823 subprocess.run(['npm', 'install'], check=True) 824 else: 825 loggy.info(f"cdk_lang ({cdk_lang}) unsupported.") 826 return False 827 828 loggy.info("cdk.install_cdk_requirements(): END") 829 return True
34def deploy(properties_env: str, lang: typing.Optional[str] = None, path: typing.Optional[str] = None) -> bool: 35 """ 36 deploy() 37 38 Runs `cdk deploy` using the properties_env to locate the `properties.env.json` file 39 40 properties_env: String containing env name to locate the properties file 41 lang: String representing the type of CDK code to deploy 42 path: String (Optional) Will search for the cdk.json file recursively if this path is not set. 43 44 Returns: True/False 45 """ 46 47 _CDK_PATH = path if path is not None else os.path.dirname(glob.glob('**/cdk.json', recursive=True)[0]) 48 _PROPS_FILE = f"properties.{properties_env}.json" 49 _CDK_DEPLOY_FILE = f"{os.getcwd()}/cdk.deploy.txt" 50 51 with _chdir(_CDK_PATH): 52 env_file = Path(_PROPS_FILE).read_text() 53 # env_data = json.load(env_file) 54 loggy.info(f"cdk.deploy(): {env_file}") 55 56 if not install_cdk_requirements(cdk_lang=lang): 57 loggy.info("cdk.deploy(): Failed to install cdk requirements. Check logs.") 58 return False 59 60 my_env = os.environ.copy() 61 my_env["ENVIRONMENT"] = properties_env 62 63 _process_output = _long_run(['cdk', 'deploy', '--require-approval', 'never', '--all'], env=my_env, check=False) 64 loggy.info("----------------------------------") 65 loggy.info(f"cdk.deploy(): CDK returned {str(_process_output.returncode)}") 66 67 with open(_CDK_DEPLOY_FILE, 'w') as file: 68 if _process_output.stderr: 69 file.write(_process_output.stderr) 70 71 if _process_output.stdout: 72 file.write(_process_output.stdout) 73 74 if _process_output.returncode != 0: 75 return False 76 77 return True
deploy()
Runs cdk deploy
using the properties_env to locate the properties.env.json
file
properties_env: String containing env name to locate the properties file lang: String representing the type of CDK code to deploy path: String (Optional) Will search for the cdk.json file recursively if this path is not set.
Returns: True/False
80def diff(properties_env: str, lang: typing.Optional[str] = None, path: typing.Optional[str] = None) -> bool: 81 """ 82 diff() 83 84 Runs `cdk diff` using the properties_env to locate the `properties.env.json` file 85 86 properties_env: String containing env name to locate the properties file 87 lang: String representing the type of CDK code to deploy (python, ts, typescript) 88 path: String (Optional) Will search for the cdk.json file recursively if this path is not set. 89 90 Returns: True/False 91 """ 92 93 _CDK_PATH = path if path is not None else os.path.dirname(glob.glob('**/cdk.json', recursive=True)[0]) 94 _PROPS_FILE = f"properties.{properties_env}.json" 95 _CDK_DIFF_FILE = f"{os.getcwd()}/cdk.diff.txt" 96 97 _EXIT = True 98 with _chdir(_CDK_PATH): 99 env_file = Path(_PROPS_FILE).read_text() 100 # env_data = json.load(env_file) 101 loggy.info(f"cdk.diff(): {env_file}") 102 103 if not install_cdk_requirements(cdk_lang=lang): 104 loggy.info("cdk.diff(): Failed to install cdk requirements. Check logs.") 105 return False 106 107 my_env = os.environ.copy() 108 my_env["ENVIRONMENT"] = properties_env 109 110 _process_output = _long_run(['cdk', 'diff', '--fail', '--verbose'], env=my_env, check=False) 111 loggy.info("----------------------------------") 112 loggy.info(f"cdk.diff(): CDK returned {str(_process_output.returncode)}") 113 # loggy.info("----------------------------------") 114 # loggy.info(f"cdk.diff(): CDK returned {str(_process_output.stderr)}") 115 # loggy.info("----------------------------------") 116 # loggy.info(f"cdk.diff(): CDK returned {str(_process_output.stdout)}") 117 # loggy.info("----------------------------------") 118 119 if _process_output.returncode != 0: 120 loggy.info("cdk.diff(): Testing for CDK Diff or Error.") 121 122 with open(_CDK_DIFF_FILE, 'w') as file: 123 file.write(_process_output.stderr) 124 125 if '[~]' in _process_output.stderr or '[+]' in _process_output.stderr or '[-]' in _process_output.stderr or '[=]' in _process_output.stderr: 126 loggy.info("cdk.diff(): CDK Diff found!") 127 _EXIT = True 128 else: 129 loggy.info("cdk.diff(): CDK ERROR!") 130 for stack in glob.glob('cdk.out/*.json'): 131 loggy.info("----------------------------------") 132 loggy.info("STACK: " + stack) 133 _stack_yaml = yaml.dump(json.loads(Path(stack).read_text())) 134 loggy.info(_stack_yaml) 135 _EXIT = False 136 137 else: 138 # if 'DEPLOY_OVERRIDE' in os.environ.keys(): 139 # print("cdkDiff.__main__(): No CDK diff! Overriding CDK_DIFF because DEPLOY_OVERRIDE") 140 # _EXIT = 0 141 # else: 142 with open(_CDK_DIFF_FILE, 'w') as file: 143 file.write("NO CDK diff found. This could be an AMI SSM Param change. Deploy proceding.") 144 145 loggy.info("cdk.diff(): NO CDK diff found. This could be an AMI SSM Param change. Deploy proceding.") 146 _EXIT = True 147 148 return _EXIT
diff()
Runs cdk diff
using the properties_env to locate the properties.env.json
file
properties_env: String containing env name to locate the properties file lang: String representing the type of CDK code to deploy (python, ts, typescript) path: String (Optional) Will search for the cdk.json file recursively if this path is not set.
Returns: True/False
151def diff_pretty(diff_file: str='cdk.diff.txt', output_file: str='cdk.diff.html', verbose: bool=False) -> bool: 152 153 _CDK_DIFF_FILE = diff_file 154 _CDK_HTML_OUTPUT_FILE = output_file 155 _VERBOSE = verbose 156 157 html_template = """ 158<!DOCTYPE html> 159<html> 160 <head> 161 <meta charset="utf-8" /> 162 <title>prettyplan</title> 163 <meta name="viewport" content="width=device-width, initial-scale=1" /> 164 <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/diff2html/bundles/css/diff2html.min.css" /> 165 <style> 166 body { 167 font-family: Arial, Helvetica, sans-serif; 168 text-rendering: optimizeLegibility; 169 background: #ecf7fe; 170 color: #000000c0; 171 font-size: 15px; 172 margin: 0; 173 } 174 @keyframes fade-in { 175 0% { 176 opacity: 0; 177 } 178 100% { 179 opacity: 1; 180 } 181 } 182 .stripe { 183 width: 100%; 184 height: 5px; 185 background: #5c4ce4; 186 animation-name: wipe-in; 187 animation-duration: 1s; 188 } 189 @keyframes wipe-in { 190 0% { 191 width: 0%; 192 } 193 100% { 194 width: 100%; 195 } 196 } 197 #release-notification { 198 background: #5c4ce4; 199 color: white; 200 font-weight: bold; 201 text-align: center; 202 overflow: hidden; 203 padding: 10px 0 15px 0; 204 height: 20px; 205 animation-name: notification-pop-in; 206 animation-duration: 2s; 207 } 208 #release-notification a { 209 color: white; 210 } 211 #release-notification.dismissed { 212 animation-name: notification-pop-out; 213 animation-duration: 0.5s; 214 height: 0; 215 padding: 0; 216 } 217 @keyframes notification-pop-in { 218 0% { 219 height: 0; 220 padding: 0; 221 } 222 50% { 223 height: 0; 224 padding: 0; 225 } 226 } 227 @keyframes notification-pop-out { 228 0% { 229 height: 20px; 230 padding: 10px 0 15px 0; 231 } 232 100% { 233 height: 0; 234 padding: 0; 235 } 236 } 237 #modal-container { 238 animation-name: fade-in; 239 animation-duration: 0.2s; 240 } 241 .modal-pane { 242 position: fixed; 243 top: 0; 244 left: 0; 245 width: 100%; 246 height: 100%; 247 background: #ffffffe6; 248 z-index: 10; 249 } 250 .modal-content { 251 position: fixed; 252 width: 60%; 253 height: 60%; 254 top: 50%; 255 left: 50%; 256 transform: translate(-50%, -50%); 257 background: #ffffff; 258 box-shadow: 0 2px 6px 0 hsla(0, 0%, 0%, 0.2); 259 z-index: 20; 260 } 261 .modal-close { 262 position: absolute; 263 right: 0; 264 padding: 10px; 265 } 266 .modal-close button.text-button { 267 color: #4526ac; 268 text-decoration: none; 269 font-weight: normal; 270 } 271 .release-notes { 272 max-width: 80%; 273 margin: 0 auto 0 auto; 274 overflow-y: auto; 275 max-height: 100%; 276 } 277 #branding { 278 float: right; 279 padding-top: 10px; 280 padding-right: 10px; 281 font-size: 10px; 282 color: #4526ac; 283 text-align: right; 284 } 285 #branding a { 286 color: #4526ac; 287 } 288 .container { 289 margin: 10px 10px 0 10px; 290 animation-name: fade-in; 291 animation-duration: 1s; 292 } 293 @media only screen and (min-width: 600px) { 294 .container { 295 max-width: 80%; 296 margin-left: auto; 297 margin-right: auto; 298 } 299 } 300 h1, 301 h2 { 302 text-align: center; 303 color: #4526ac; 304 } 305 #terraform-plan { 306 width: 100%; 307 min-height: 300px; 308 border: none; 309 box-shadow: 0 2px 6px 0 hsla(0, 0%, 0%, 0.2); 310 padding: 10px; 311 margin-bottom: 10px; 312 resize: none; 313 background: #ffffffe6; 314 } 315 button { 316 font-size: 18px; 317 background: #5c4ce4; 318 color: #fff; 319 box-shadow: 0 2px 6px 0 hsla(0, 0%, 0%, 0.2); 320 border: none; 321 border-radius: 2px; 322 min-width: 170px; 323 height: 40px; 324 } 325 button:hover { 326 background: #6567ea; 327 cursor: pointer; 328 } 329 button:active { 330 background: #5037ca; 331 } 332 button.text-button { 333 background: none; 334 box-shadow: none; 335 border-radius: 0; 336 width: auto; 337 height: auto; 338 text-decoration: underline; 339 font-size: inherit; 340 font-weight: inherit; 341 font-family: Arial, Helvetica, sans-serif; 342 color: inherit; 343 text-align: inherit; 344 padding: 0; 345 } 346 #parsing-error-message { 347 background-color: #ffffff; 348 padding: 10px; 349 color: #000000c0; 350 margin: 4px; 351 box-shadow: 0 2px 6px 0 hsla(0, 0%, 0%, 0.2); 352 font-weight: bold; 353 border-left: 2px solid red; 354 animation-name: error; 355 animation-duration: 1s; 356 } 357 @keyframes error { 358 0% { 359 background-color: red; 360 } 361 100% { 362 background-color: white; 363 } 364 } 365 .prettyplan ul { 366 padding-left: 0; 367 font-size: 13px; 368 } 369 .prettyplan li { 370 list-style: none; 371 background: #ffffffe6; 372 padding: 10px; 373 color: #000000c0; 374 margin: 4px; 375 box-shadow: 0 2px 6px 0 hsla(0, 0%, 0%, 0.2); 376 } 377 .prettyplan ul.warnings li { 378 border-left: 3px solid #757575; 379 } 380 .prettyplan ul.actions li.update { 381 border-left: 3px solid #ff8f00; 382 } 383 .prettyplan ul.actions li.create { 384 border-left: 3px solid #2e7d32; 385 } 386 .prettyplan ul.actions li.addition { 387 border-left: 3px solid #2e7d32; 388 } 389 .prettyplan ul.actions li.destroy { 390 border-left: 3px solid #b71c1c; 391 } 392 .prettyplan ul.actions li.removal { 393 border-left: 3px solid #b71c1c; 394 } 395 .prettyplan ul.actions li.recreate { 396 border-left: 3px solid #1565c0; 397 } 398 .prettyplan ul.actions li.read { 399 border-left: 3px solid #519bf0; 400 } 401 .badge { 402 display: inline-block; 403 text-transform: uppercase; 404 margin-right: 10px; 405 padding: 3px; 406 font-size: 12px; 407 font-weight: bold; 408 } 409 .warnings .badge { 410 color: #757575; 411 } 412 li.update .badge { 413 color: #ff8f00; 414 } 415 li.create .badge { 416 color: #2e7d32; 417 } 418 li.addition .badge { 419 color: #2e7d32; 420 } 421 li.destroy .badge { 422 color: #b71c1c; 423 } 424 li.removal .badge { 425 color: #b71c1c; 426 } 427 li.recreate .badge { 428 color: #1565c0; 429 } 430 li.read .badge { 431 color: #519bf0; 432 } 433 .id-segment:not(:last-child)::after { 434 content: ' > '; 435 } 436 .id-segment.name, 437 .id-segment.type { 438 font-weight: bold; 439 } 440 .change-count { 441 float: right; 442 } 443 .summary { 444 cursor: pointer; 445 } 446 .no-diff-changes-breakdown { 447 margin: 5px auto 0 auto; 448 padding: 5px; 449 } 450 .no-diff-changes-breakdown table { 451 width: 100%; 452 word-break: break-all; 453 font-size: 13px; 454 } 455 .no-diff-changes-breakdown table td { 456 padding: 10px; 457 width: 40%; 458 } 459 pre { 460 white-space: pre-wrap; 461 background: #f3f3f3; 462 } 463 .no-diff-changes-breakdown table td.property { 464 width: 20%; 465 text-align: right; 466 font-weight: bold; 467 } 468 .no-diff-changes-breakdown table tr:nth-child(even) { 469 background-color: #f5f5f5; 470 } 471 .forces-new-resource { 472 color: #b71c1c; 473 } 474 .collapsed, 475 .hidden { 476 display: none; 477 } 478 .actions button { 479 background: none; 480 border: none; 481 text-decoration: underline; 482 color: black; 483 box-shadow: none; 484 font-weight: bold; 485 font-size: 14px; 486 } 487 .d2h-icon { 488 display: none; 489 } 490 </style> 491 </head> 492 <body> 493 <div class="stripe"></div> 494 <div class="container"> 495 <h1>prettyplan</h1> 496 <div id="parsing-error-message" class="hidden"> 497 That doesn't look like a Terraform plan. Did you copy the entire output (without colouring) from the plan 498 command? 499 </div> 500 <div id="prettyplan" class="prettyplan"> 501 <ul id="errors" class="errors"></ul> 502 <ul id="warnings" class="warnings"></ul> 503 <button class="expand-all" onclick="expandAll()">Expand all</button> 504 <button class="collapse-all hidden" onclick="collapseAll()">Collapse all</button> 505 <div id="stacks"></div> 506 <ul id="actions" class="actions"></ul> 507 <pre id="diff"></pre> 508 </div> 509 </div> 510 <script> 511 function accordion(element) { 512 const changes = element.parentElement.getElementsByClassName('changes'); 513 for (var i = 0; i < changes.length; i++) { 514 toggleClass(changes[i], 'collapsed'); 515 } 516 } 517 function toggleClass(element, className) { 518 if (!element.className.match(className)) { 519 element.className += ' ' + className; 520 } else { 521 element.className = element.className.replace(className, ''); 522 } 523 } 524 function addClass(element, className) { 525 if (!element.className.match(className)) element.className += ' ' + className; 526 } 527 function removeClass(element, className) { 528 element.className = element.className.replace(className, ''); 529 } 530 function expandAll() { 531 const sections = document.querySelectorAll('.changes.collapsed'); 532 for (var i = 0; i < sections.length; i++) { 533 toggleClass(sections[i], 'collapsed'); 534 } 535 toggleClass(document.querySelector('.expand-all'), 'hidden'); 536 toggleClass(document.querySelector('.collapse-all'), 'hidden'); 537 } 538 function collapseAll() { 539 const sections = document.querySelectorAll('.changes:not(.collapsed)'); 540 for (var i = 0; i < sections.length; i++) { 541 toggleClass(sections[i], 'collapsed'); 542 } 543 toggleClass(document.querySelector('.expand-all'), 'hidden'); 544 toggleClass(document.querySelector('.collapse-all'), 'hidden'); 545 } 546 function removeChildren(element) { 547 while (element.lastChild) { 548 element.removeChild(element.lastChild); 549 } 550 } 551 function createModalContainer() { 552 const modalElement = document.createElement('div'); 553 modalElement.id = 'modal-container'; 554 document.body.appendChild(modalElement); 555 return modalElement; 556 } 557 function closeModal() { 558 const modalElement = document.getElementById('modal-container'); 559 document.body.removeChild(modalElement); 560 } 561 </script> 562 </body> 563</html> 564""" 565 566 if _VERBOSE: 567 loggy.info("diff_pretty()): BEGIN") 568 569 if not Path(_CDK_DIFF_FILE).exists(): 570 loggy.info(f"diff_pretty(): ERROR. INPUT FILE ({_CDK_DIFF_FILE}) does NOT exist.") 571 return False 572 573 if _VERBOSE: 574 loggy.info(f"diff_pretty(): Reading input file: {_CDK_DIFF_FILE}") 575 576 cdk_out = Path(_CDK_DIFF_FILE).read_text() 577 578 if _VERBOSE: 579 loggy.info(f"diff_pretty(): {cdk_out}") 580 581 # 582 # Here, we split the stack output into an array based on `Stack xyz` lines 583 # 584 stacks = [f"Stack {e}" for e in cdk_out.split('Stack ') if e] 585 586 # 587 # This is a Hack to get rid of any warnings at the top of the output before 588 # we encounter our first `Stack xyz` line. 589 # 590 for idx, l in enumerate(stacks): 591 match = False 592 for hack_line in cdk_out.split('\n'): 593 if l.split('\n')[0] in hack_line: 594 match = True 595 break 596 597 if not match: 598 del stacks[idx] 599 continue 600 601 # 602 # Convert each `Stack xyz` line to our HTML format 603 # 604 for idx, l in enumerate(stacks): 605 new_line = [] 606 diff_type = "" 607 divs_open = 0 608 ul_li_open = 0 609 for ndx, n in enumerate(l.split('\n')): 610 if _VERBOSE: 611 loggy.info(f"diff_pretty(): DEBUG: ({str(idx)})-({str(ndx)}) - {str(n)}") 612 613 if ndx == 0: 614 # new_line.append(f"<div class=\"stack\"><h2>{n}</h2><div class=\"raw-diff\"><button onclick=\"accordion(this)\">Expand this Stack</button><div class=\"changes\">") 615 616 # 617 # 2 open divs 618 # 619 divs_open += 2 620 new_line.append(f"<div class=\"stack\"><h2>{n}</h2><button onclick=\"accordion(this)\">Expand this Stack</button><div class=\"changes\">") 621 else: 622 if n.startswith('IAM Policy Changes'): 623 diff_type = "IAM" 624 elif n.startswith('Resources'): 625 diff_type = "Resources" 626 elif n.startswith('Outputs'): 627 diff_type = "Outputs" 628 elif n.startswith('There were no differences'): 629 diff_type = "NoDiff" 630 631 if 'Resources' in diff_type or 'Outputs' in diff_type: 632 if n.startswith('[+]'): 633 if ul_li_open > 0: 634 ul_li_open = 0 635 divs_open -= 1 636 new_line.append('<pre></div></li></ul>') 637 638 ul_li_open += 1 639 divs_open += 1 640 new_line.append(f"<ul class=\"actions\"><li class=\"create\"><div class=\"summary\" onclick=\"accordion(this)\"><span class=\"badge\">Addition</span></div><div class\"changes\"><pre>{n}") 641 elif n.startswith('[-]'): 642 if ul_li_open > 0: 643 ul_li_open = 0 644 divs_open -= 1 645 new_line.append('</pre></div></li></ul>') 646 647 ul_li_open += 1 648 divs_open += 1 649 new_line.append(f"<ul class=\"actions\"><li class=\"destroy\"><div class=\"summary\" onclick=\"accordion(this)\"><span class=\"badge\">Delete</span></div><div class\"changes\"><pre>{n}") 650 elif n.startswith('[~]'): 651 if ul_li_open > 0: 652 ul_li_open = 0 653 divs_open -= 1 654 new_line.append('</pre></div></li></ul>') 655 656 ul_li_open += 1 657 divs_open += 1 658 if 'replace' in n or ((len(l)-1) >= ndx+1 and 'replace' in l[ndx+1]): 659 new_line.append(f"<ul class=\"actions\"><li class=\"destroy\"><div class=\"summary\" onclick=\"accordion(this)\"><span class=\"badge\">REPLACEMENT</span></div><div class\"changes\"><pre>{n}") 660 else: 661 new_line.append(f"<ul class=\"actions\"><li class=\"update\"><div class=\"summary\" onclick=\"accordion(this)\"><span class=\"badge\">Update</span></div><div class\"changes\"><pre>{n}") 662 663 else: 664 new_line.append(f"{n}") 665 666 elif 'IAM' in diff_type: 667 if divs_open <= 2: 668 ul_li_open += 1 669 divs_open += 1 670 new_line.append(f"<ul class=\"actions\"><li class=\"update\"><div class=\"summary\" onclick=\"accordion(this)\"><span class=\"badge\">Update</span></div><div class\"changes\"><pre>{n}") 671 else: 672 new_line.append(f"{n}") 673 674 elif 'NoDiff' in diff_type: 675 if divs_open <= 2: 676 ul_li_open += 1 677 divs_open += 1 678 new_line.append(f"<ul class=\"actions\"><li class=\"read\"><div class=\"summary\" onclick=\"accordion(this)\"><span class=\"badge\">No Diff</span></div><div class\"changes\"><pre>{n}") 679 else: 680 new_line.append(f"{n}") 681 682 while ul_li_open > 0: 683 ul_li_open -= 1 684 divs_open -= 1 685 new_line.append('</pre></div></li></ul>') 686 687 while divs_open > 0: 688 divs_open -= 1 689 new_line.append('</div>') 690 691 stacks[idx] = '\n'.join(new_line) 692 693 # 694 # If there are no stacks, then we can print this as a giant error 695 # 696 if not stacks: 697 print("cdk-diff-pretty.__main__(): ERROR: No stacks found. Outputting diff as ERROR.") 698 stacks.append("<div class=\"stack\"><h2>CDK DIFF ERROR</h2><button onclick=\"accordion(this)\">Expand this Stack</button><div class=\"changes\">") 699 stacks.append("<ul class=\"actions\"><li class=\"destroy\"><div class=\"summary\" onclick=\"accordion(this)\"><span class=\"badge\">CDK DIFF ERROR</span></div><div class\"changes\"><pre>") 700 stacks.append(f"{cdk_out}") 701 stacks.append('</pre></div></li></ul>') 702 stacks.append('</div></div>') 703 704 html_template = html_template.replace('<h1>prettyplan</h1>', '<h1>CDK Diff</h1>') 705 html_template = html_template.replace('<title>prettyplan</title>', '<title>CDK Diff</title>') 706 707 html_template = html_template.replace('<div id="stacks"></div>', f"<div id=\"stacks\">{' '.join(stacks)}</div>") 708 709 if _VERBOSE: 710 loggy.info(f"diff_pretty(): Writing output file to {_CDK_HTML_OUTPUT_FILE}") 711 712 Path(_CDK_HTML_OUTPUT_FILE).write_text(html_template) 713 if _VERBOSE: 714 loggy.info("diff_pretty(): END") 715 716 return True
719def get_cdk_installed_version() -> str: 720 """ 721 get_cdk_installed_version() 722 723 Get the CDK installed version. 724 725 Returns: String representing the installed version 726 """ 727 728 loggy.info("cdk.get_cdk_installed_version(): BEGIN") 729 process_output = subprocess.run(['cdk', '--version'], stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True) 730 loggy.info("cdk.get_cdk_installed_version(): BEGIN") 731 return process_output.stdout.decode().split(' ')[0]
get_cdk_installed_version()
Get the CDK installed version.
Returns: String representing the installed version
734def get_cdk_required_version() -> str: 735 """ 736 get_cdk_required_version() 737 738 Get the CDK required version. If `cdk_lock_version` file exists, pulls the 739 version value from there. Otherwise, returns the installed version of CDK. 740 741 Returns: String representing the required version 742 """ 743 loggy.info("cdk.get_cdk_required_version(): BEGIN") 744 if os.path.exists('cdk_lock_version'): 745 _CDK_REQUIRED_VERSION = Path('cdk_lock_version').read_text().strip() 746 else: 747 _CDK_REQUIRED_VERSION = get_cdk_installed_version() 748 749 loggy.info("cdk.get_cdk_required_version(): END") 750 return _CDK_REQUIRED_VERSION
get_cdk_required_version()
Get the CDK required version. If cdk_lock_version
file exists, pulls the
version value from there. Otherwise, returns the installed version of CDK.
Returns: String representing the required version
753def set_cdk_installed_version() -> str: 754 """ 755 set_cdk_installed_version() 756 757 Set the CDK installed version. If the required version of CDK is different 758 from the installed version, run the npm commands to install the correct 759 version. 760 761 Returns: String representing the required version 762 """ 763 764 loggy.info("cdk.set_cdk_installed_version(): BEGIN") 765 _CDK_REQUIRED_VERSION = get_cdk_required_version() 766 _CDK_INSTALLED_VERSION = get_cdk_installed_version() 767 loggy.info("cdk.set_cdk_installed_version(): _CDK_REQUIRED_VERSION: " + _CDK_REQUIRED_VERSION) 768 loggy.info("cdk.set_cdk_installed_version(): _CDK_INSTALLED_VERSION: " + _CDK_INSTALLED_VERSION) 769 770 if _CDK_REQUIRED_VERSION != _CDK_INSTALLED_VERSION: 771 subprocess.run(['sudo', 'npm', 'install', '-g', 'aws-cdk@' + _CDK_REQUIRED_VERSION], stdout=subprocess.PIPE, stderr=subprocess.PIPE, check=True) 772 773 loggy.info("cdk.set_cdk_installed_version(): END") 774 return _CDK_REQUIRED_VERSION
set_cdk_installed_version()
Set the CDK installed version. If the required version of CDK is different from the installed version, run the npm commands to install the correct version.
Returns: String representing the required version
777def install_cdk_requirements(cdk_lang: str) -> bool: 778 """ 779 install_cdk_requirements() 780 781 Install requirements for the specific type/language of cdk deployment. 782 783 cdk_lang: String representing the type of CDK code to deploy 784 785 Returns: True/False 786 """ 787 loggy.info("cdk.install_cdk_requirements(): BEGIN") 788 _cdk_required_version = set_cdk_installed_version() 789 if cdk_lang == 'python': 790 loggy.info("cdk.install_cdk_requirements(): Installing python requirements.") 791 792 if os.path.exists('setup.py'): 793 loggy.info("cdk.install_cdk_requirements(): Original setup.py file.") 794 _file_contents = Path('setup.py').read_text() 795 loggy.info(_file_contents) 796 797 # 798 # TAW 20220529 - Setting required version changes between cdk v1 and v2 799 # 800 if _cdk_required_version.startswith('1'): 801 loggy.info("cdk.install_cdk_requirements(): Detected cdk v1. Adding version number to aws_cdk.aws_*.") 802 _file_contents = re.sub('aws_cdk.aws_(.*)"', r'aws_cdk.aws_\1=='+_cdk_required_version+'"', _file_contents) 803 elif _cdk_required_version.startswith('2'): 804 loggy.info("cdk.install_cdk_requirements(): Detected cdk v2. Adding version number to aws-cdk-lib*.") 805 _file_contents = re.sub('aws-cdk-lib(.*)"', r'aws-cdk-lib\1=='+_cdk_required_version+'"', _file_contents) 806 else: 807 loggy.info("cdk.install_cdk_requirements(): Detected unknown cdk version. You might need to modify cdk.py in gocd library to support this.") 808 809 with open('setup.py', 'w') as file: 810 file.write(_file_contents) 811 812 # subprocess.run(['sed', '-i', '-E', '\'s|aws_cdk.aws_(.*)"|aws_cdk.aws_\1=='+_cdk_required_version+'"|g\'', 'setup.py'], check=True) 813 # sed -i -E 's|aws_cdk.aws_(.*)"|aws_cdk.aws_\1=='$_CDK_REQUIRED_VERSION'"|g' setup.py 814 815 loggy.info("cdk.install_cdk_requirements(): Modified setup.py file.") 816 _new_file_contents = Path('setup.py').read_text() 817 loggy.info(_new_file_contents) 818 819 subprocess.run(['pip3', 'install', '-r', 'requirements.txt'], check=True) 820 else: 821 loggy.info("cdk.install_cdk_requirements(): Error. setup.py does not exist.") 822 elif cdk_lang == 'ts' or cdk_lang == 'typescript': 823 loggy.info("cdk.install_cdk_requirements(): Installing npm requirements.") 824 subprocess.run(['npm', 'install'], check=True) 825 else: 826 loggy.info(f"cdk_lang ({cdk_lang}) unsupported.") 827 return False 828 829 loggy.info("cdk.install_cdk_requirements(): END") 830 return True
install_cdk_requirements()
Install requirements for the specific type/language of cdk deployment.
cdk_lang: String representing the type of CDK code to deploy
Returns: True/False