1""" 2Management of zc.buildout 3 4.. versionadded:: 2014.1.0 5 6.. _`minitage's buildout maker`: https://github.com/minitage/minitage/blob/master/src/minitage/core/makers/buildout.py 7 8This module is inspired by `minitage's buildout maker`_ 9 10.. note:: 11 12 The zc.buildout integration is still in beta; the API is subject to change 13 14General notes 15------------- 16 17You have those following methods: 18 19* upgrade_bootstrap 20* bootstrap 21* run_buildout 22* buildout 23""" 24 25 26import copy 27import logging 28import os 29import re 30import sys 31import traceback 32import urllib.request 33 34import salt.utils.files 35import salt.utils.path 36import salt.utils.stringutils 37from salt.exceptions import CommandExecutionError 38 39INVALID_RESPONSE = "Unexpected response from buildout" 40VALID_RESPONSE = "" 41NOTSET = object() 42HR = "{}\n".format("-" * 80) 43RE_F = re.S | re.M | re.U 44BASE_STATUS = { 45 "status": None, 46 "logs": [], 47 "comment": "", 48 "out": None, 49 "logs_by_level": {}, 50 "outlog": None, 51 "outlog_by_level": None, 52} 53_URL_VERSIONS = { 54 1: "http://downloads.buildout.org/1/bootstrap.py", 55 2: "http://downloads.buildout.org/2/bootstrap.py", 56} 57DEFAULT_VER = 2 58_logger = logging.getLogger(__name__) 59 60# Define the module's virtual name 61__virtualname__ = "buildout" 62 63 64def __virtual__(): 65 """ 66 Only load if buildout libs are present 67 """ 68 return __virtualname__ 69 70 71def _salt_callback(func, **kwargs): 72 LOG.clear() 73 74 def _call_callback(*a, **kw): 75 # cleanup the module kwargs before calling it from the 76 # decorator 77 kw = copy.deepcopy(kw) 78 for k in [ar for ar in kw if "__pub" in ar]: 79 kw.pop(k, None) 80 st = BASE_STATUS.copy() 81 directory = kw.get("directory", ".") 82 onlyif = kw.get("onlyif", None) 83 unless = kw.get("unless", None) 84 runas = kw.get("runas", None) 85 env = kw.get("env", ()) 86 status = BASE_STATUS.copy() 87 try: 88 # may rise _ResultTransmission 89 status = _check_onlyif_unless( 90 onlyif, unless, directory=directory, runas=runas, env=env 91 ) 92 # if onlyif/unless returns, we are done 93 if status is None: 94 status = BASE_STATUS.copy() 95 comment, st = "", True 96 out = func(*a, **kw) 97 # we may have already final statuses not to be touched 98 # merged_statuses flag is there to check that ! 99 if not isinstance(out, dict): 100 status = _valid(status, out=out) 101 else: 102 if out.get("merged_statuses", False): 103 status = out 104 else: 105 status = _set_status( 106 status, 107 status=out.get("status", True), 108 comment=out.get("comment", ""), 109 out=out.get("out", out), 110 ) 111 except Exception: # pylint: disable=broad-except 112 trace = traceback.format_exc() 113 LOG.error(trace) 114 _invalid(status) 115 LOG.clear() 116 # before returning, trying to compact the log output 117 for k in ["comment", "out", "outlog"]: 118 if status[k] and isinstance(status[k], str): 119 status[k] = "\n".join( 120 [log for log in status[k].split("\n") if log.strip()] 121 ) 122 return status 123 124 _call_callback.__doc__ = func.__doc__ 125 return _call_callback 126 127 128class _Logger: 129 levels = ("info", "warn", "debug", "error") 130 131 def __init__(self): 132 self._msgs = [] 133 self._by_level = {} 134 135 def _log(self, level, msg): 136 if not isinstance(msg, str): 137 msg = msg.decode("utf-8") 138 if level not in self._by_level: 139 self._by_level[level] = [] 140 self._msgs.append((level, msg)) 141 self._by_level[level].append(msg) 142 143 def debug(self, msg): 144 self._log("debug", msg) 145 146 def info(self, msg): 147 self._log("info", msg) 148 149 def error(self, msg): 150 self._log("error", msg) 151 152 def warn(self, msg): 153 self._log("warn", msg) 154 155 warning = warn 156 157 def clear(self): 158 for i in self._by_level: 159 self._by_level[i] = [] 160 for i in self._msgs[:]: 161 self._msgs.pop() 162 163 def get_logs(self, level): 164 return self._by_level.get(level, []) 165 166 @property 167 def messages(self): 168 return self._msgs 169 170 @property 171 def by_level(self): 172 return self._by_level 173 174 175LOG = _Logger() 176 177 178def _encode_status(status): 179 if status["out"] is None: 180 status["out"] = None 181 else: 182 status["out"] = salt.utils.stringutils.to_unicode(status["out"]) 183 status["outlog_by_level"] = salt.utils.stringutils.to_unicode( 184 status["outlog_by_level"] 185 ) 186 if status["logs"]: 187 for i, data in enumerate(status["logs"][:]): 188 status["logs"][i] = (data[0], salt.utils.stringutils.to_unicode(data[1])) 189 for logger in "error", "warn", "info", "debug": 190 logs = status["logs_by_level"].get(logger, [])[:] 191 if logs: 192 for i, log in enumerate(logs): 193 status["logs_by_level"][logger][ 194 i 195 ] = salt.utils.stringutils.to_unicode(log) 196 return status 197 198 199def _set_status(m, comment=INVALID_RESPONSE, status=False, out=None): 200 """ 201 Assign status data to a dict. 202 """ 203 m["out"] = out 204 m["status"] = status 205 m["logs"] = LOG.messages[:] 206 m["logs_by_level"] = LOG.by_level.copy() 207 outlog, outlog_by_level = "", "" 208 m["comment"] = comment 209 if out and isinstance(out, str): 210 outlog += HR 211 outlog += "OUTPUT:\n" 212 outlog += "{}\n".format(salt.utils.stringutils.to_unicode(out)) 213 outlog += HR 214 if m["logs"]: 215 outlog += HR 216 outlog += "Log summary:\n" 217 outlog += HR 218 outlog_by_level += HR 219 outlog_by_level += "Log summary by level:\n" 220 outlog_by_level += HR 221 for level, msg in m["logs"]: 222 outlog += "\n{}: {}\n".format( 223 level.upper(), salt.utils.stringutils.to_unicode(msg) 224 ) 225 for logger in "error", "warn", "info", "debug": 226 logs = m["logs_by_level"].get(logger, []) 227 if logs: 228 outlog_by_level += "\n{}:\n".format(logger.upper()) 229 for idx, log in enumerate(logs[:]): 230 logs[idx] = salt.utils.stringutils.to_unicode(log) 231 outlog_by_level += "\n".join(logs) 232 outlog_by_level += "\n" 233 outlog += HR 234 m["outlog"] = outlog 235 m["outlog_by_level"] = outlog_by_level 236 return _encode_status(m) 237 238 239def _invalid(m, comment=INVALID_RESPONSE, out=None): 240 """ 241 Return invalid status. 242 """ 243 return _set_status(m, status=False, comment=comment, out=out) 244 245 246def _valid(m, comment=VALID_RESPONSE, out=None): 247 """ 248 Return valid status. 249 """ 250 return _set_status(m, status=True, comment=comment, out=out) 251 252 253def _Popen( 254 command, 255 output=False, 256 directory=".", 257 runas=None, 258 env=(), 259 exitcode=0, 260 use_vt=False, 261 loglevel=None, 262): 263 """ 264 Run a command. 265 266 output 267 return output if true 268 269 directory 270 directory to execute in 271 272 runas 273 user used to run buildout as 274 275 env 276 environment variables to set when running 277 278 exitcode 279 fails if cmd does not return this exit code 280 (set to None to disable check) 281 282 use_vt 283 Use the new salt VT to stream output [experimental] 284 285 """ 286 ret = None 287 directory = os.path.abspath(directory) 288 if isinstance(command, list): 289 command = " ".join(command) 290 LOG.debug("Running {}".format(command)) # pylint: disable=str-format-in-logging 291 if not loglevel: 292 loglevel = "debug" 293 ret = __salt__["cmd.run_all"]( 294 command, 295 cwd=directory, 296 output_loglevel=loglevel, 297 runas=runas, 298 env=env, 299 use_vt=use_vt, 300 python_shell=False, 301 ) 302 out = ret["stdout"] + "\n\n" + ret["stderr"] 303 if (exitcode is not None) and (ret["retcode"] != exitcode): 304 raise _BuildoutError(out) 305 ret["output"] = out 306 if output: 307 ret = out 308 return ret 309 310 311class _BuildoutError(CommandExecutionError): 312 """ 313 General Buildout Error. 314 """ 315 316 317def _has_old_distribute(python=sys.executable, runas=None, env=()): 318 old_distribute = False 319 try: 320 cmd = [ 321 python, 322 "-c", 323 "'import pkg_resources;" 324 "print pkg_resources." 325 'get_distribution("distribute").location\'', 326 ] 327 ret = _Popen(cmd, runas=runas, env=env, output=True) 328 if "distribute-0.6" in ret: 329 old_distribute = True 330 except Exception: # pylint: disable=broad-except 331 old_distribute = False 332 return old_distribute 333 334 335def _has_setuptools7(python=sys.executable, runas=None, env=()): 336 new_st = False 337 try: 338 cmd = [ 339 python, 340 "-c", 341 "'import pkg_resources;" 342 "print not pkg_resources." 343 'get_distribution("setuptools").version.startswith("0.6")\'', 344 ] 345 ret = _Popen(cmd, runas=runas, env=env, output=True) 346 if "true" in ret.lower(): 347 new_st = True 348 except Exception: # pylint: disable=broad-except 349 new_st = False 350 return new_st 351 352 353def _find_cfgs(path, cfgs=None): 354 """ 355 Find all buildout configs in a subdirectory. 356 only buildout.cfg and etc/buildout.cfg are valid in:: 357 358 path 359 directory where to start to search 360 361 cfg 362 a optional list to append to 363 364 . 365 ├── buildout.cfg 366 ├── etc 367 │ └── buildout.cfg 368 ├── foo 369 │ └── buildout.cfg 370 └── var 371 └── buildout.cfg 372 """ 373 ignored = ["var", "parts"] 374 dirs = [] 375 if not cfgs: 376 cfgs = [] 377 for i in os.listdir(path): 378 fi = os.path.join(path, i) 379 if fi.endswith(".cfg") and os.path.isfile(fi): 380 cfgs.append(fi) 381 if os.path.isdir(fi) and (i not in ignored): 382 dirs.append(fi) 383 for fpath in dirs: 384 for p, ids, ifs in salt.utils.path.os_walk(fpath): 385 for i in ifs: 386 if i.endswith(".cfg"): 387 cfgs.append(os.path.join(p, i)) 388 return cfgs 389 390 391def _get_bootstrap_content(directory="."): 392 """ 393 Get the current bootstrap.py script content 394 """ 395 try: 396 with salt.utils.files.fopen( 397 os.path.join(os.path.abspath(directory), "bootstrap.py") 398 ) as fic: 399 oldcontent = salt.utils.stringutils.to_unicode(fic.read()) 400 except OSError: 401 oldcontent = "" 402 return oldcontent 403 404 405def _get_buildout_ver(directory="."): 406 """Check for buildout versions. 407 408 In any cases, check for a version pinning 409 Also check for buildout.dumppickedversions which is buildout1 specific 410 Also check for the version targeted by the local bootstrap file 411 Take as default buildout2 412 413 directory 414 directory to execute in 415 """ 416 directory = os.path.abspath(directory) 417 buildoutver = 2 418 try: 419 files = _find_cfgs(directory) 420 for f in files: 421 with salt.utils.files.fopen(f) as fic: 422 buildout1re = re.compile(r"^zc\.buildout\s*=\s*1", RE_F) 423 dfic = salt.utils.stringutils.to_unicode(fic.read()) 424 if ("buildout.dumppick" in dfic) or (buildout1re.search(dfic)): 425 buildoutver = 1 426 bcontent = _get_bootstrap_content(directory) 427 if ( 428 "--download-base" in bcontent 429 or "--setup-source" in bcontent 430 or "--distribute" in bcontent 431 ): 432 buildoutver = 1 433 except OSError: 434 pass 435 return buildoutver 436 437 438def _get_bootstrap_url(directory): 439 """ 440 Get the most appropriate download URL for the bootstrap script. 441 442 directory 443 directory to execute in 444 445 """ 446 v = _get_buildout_ver(directory) 447 return _URL_VERSIONS.get(v, _URL_VERSIONS[DEFAULT_VER]) 448 449 450def _dot_buildout(directory): 451 """ 452 Get the local marker directory. 453 454 directory 455 directory to execute in 456 """ 457 return os.path.join(os.path.abspath(directory), ".buildout") 458 459 460@_salt_callback 461def upgrade_bootstrap( 462 directory=".", 463 onlyif=None, 464 unless=None, 465 runas=None, 466 env=(), 467 offline=False, 468 buildout_ver=None, 469): 470 """ 471 Upgrade current bootstrap.py with the last released one. 472 473 Indeed, when we first run a buildout, a common source of problem 474 is to have a locally stale bootstrap, we just try to grab a new copy 475 476 directory 477 directory to execute in 478 479 offline 480 are we executing buildout in offline mode 481 482 buildout_ver 483 forcing to use a specific buildout version (1 | 2) 484 485 onlyif 486 Only execute cmd if statement on the host return 0 487 488 unless 489 Do not execute cmd if statement on the host return 0 490 491 CLI Example: 492 493 .. code-block:: bash 494 495 salt '*' buildout.upgrade_bootstrap /srv/mybuildout 496 """ 497 if buildout_ver: 498 booturl = _URL_VERSIONS[buildout_ver] 499 else: 500 buildout_ver = _get_buildout_ver(directory) 501 booturl = _get_bootstrap_url(directory) 502 LOG.debug("Using {}".format(booturl)) # pylint: disable=str-format-in-logging 503 # try to download an up-to-date bootstrap 504 # set defaulttimeout 505 # and add possible content 506 directory = os.path.abspath(directory) 507 b_py = os.path.join(directory, "bootstrap.py") 508 comment = "" 509 try: 510 oldcontent = _get_bootstrap_content(directory) 511 dbuild = _dot_buildout(directory) 512 data = oldcontent 513 updated = False 514 dled = False 515 if not offline: 516 try: 517 if not os.path.isdir(dbuild): 518 os.makedirs(dbuild) 519 # only try to download once per buildout checkout 520 with salt.utils.files.fopen( 521 os.path.join(dbuild, "{}.updated_bootstrap".format(buildout_ver)) 522 ): 523 pass 524 except OSError: 525 LOG.info("Bootstrap updated from repository") 526 data = urllib.request.urlopen(booturl).read() 527 updated = True 528 dled = True 529 if "socket.setdefaulttimeout" not in data: 530 updated = True 531 ldata = data.splitlines() 532 ldata.insert(1, "import socket;socket.setdefaulttimeout(2)") 533 data = "\n".join(ldata) 534 if updated: 535 comment = "Bootstrap updated" 536 with salt.utils.files.fopen(b_py, "w") as fic: 537 fic.write(salt.utils.stringutils.to_str(data)) 538 if dled: 539 with salt.utils.files.fopen( 540 os.path.join(dbuild, "{}.updated_bootstrap".format(buildout_ver)), "w" 541 ) as afic: 542 afic.write("foo") 543 except OSError: 544 if oldcontent: 545 with salt.utils.files.fopen(b_py, "w") as fic: 546 fic.write(salt.utils.stringutils.to_str(oldcontent)) 547 548 return {"comment": comment} 549 550 551@_salt_callback 552def bootstrap( 553 directory=".", 554 config="buildout.cfg", 555 python=sys.executable, 556 onlyif=None, 557 unless=None, 558 runas=None, 559 env=(), 560 distribute=None, 561 buildout_ver=None, 562 test_release=False, 563 offline=False, 564 new_st=None, 565 use_vt=False, 566 loglevel=None, 567): 568 """ 569 Run the buildout bootstrap dance (python bootstrap.py). 570 571 directory 572 directory to execute in 573 574 config 575 alternative buildout configuration file to use 576 577 runas 578 User used to run buildout as 579 580 env 581 environment variables to set when running 582 583 buildout_ver 584 force a specific buildout version (1 | 2) 585 586 test_release 587 buildout accept test release 588 589 offline 590 are we executing buildout in offline mode 591 592 distribute 593 Forcing use of distribute 594 595 new_st 596 Forcing use of setuptools >= 0.7 597 598 python 599 path to a python executable to use in place of default (salt one) 600 601 onlyif 602 Only execute cmd if statement on the host return 0 603 604 unless 605 Do not execute cmd if statement on the host return 0 606 607 use_vt 608 Use the new salt VT to stream output [experimental] 609 610 CLI Example: 611 612 .. code-block:: bash 613 614 salt '*' buildout.bootstrap /srv/mybuildout 615 """ 616 directory = os.path.abspath(directory) 617 dbuild = _dot_buildout(directory) 618 bootstrap_args = "" 619 has_distribute = _has_old_distribute(python=python, runas=runas, env=env) 620 has_new_st = _has_setuptools7(python=python, runas=runas, env=env) 621 if has_distribute and has_new_st and not distribute and new_st: 622 new_st = True 623 distribute = False 624 if has_distribute and has_new_st and not distribute and new_st: 625 new_st = True 626 distribute = False 627 if has_distribute and has_new_st and distribute and not new_st: 628 new_st = True 629 distribute = False 630 if has_distribute and has_new_st and not distribute and not new_st: 631 new_st = True 632 distribute = False 633 634 if not has_distribute and has_new_st and not distribute and new_st: 635 new_st = True 636 distribute = False 637 if not has_distribute and has_new_st and not distribute and new_st: 638 new_st = True 639 distribute = False 640 if not has_distribute and has_new_st and distribute and not new_st: 641 new_st = True 642 distribute = False 643 if not has_distribute and has_new_st and not distribute and not new_st: 644 new_st = True 645 distribute = False 646 647 if has_distribute and not has_new_st and not distribute and new_st: 648 new_st = True 649 distribute = False 650 if has_distribute and not has_new_st and not distribute and new_st: 651 new_st = True 652 distribute = False 653 if has_distribute and not has_new_st and distribute and not new_st: 654 new_st = False 655 distribute = True 656 if has_distribute and not has_new_st and not distribute and not new_st: 657 new_st = False 658 distribute = True 659 660 if not has_distribute and not has_new_st and not distribute and new_st: 661 new_st = True 662 distribute = False 663 if not has_distribute and not has_new_st and not distribute and new_st: 664 new_st = True 665 distribute = False 666 if not has_distribute and not has_new_st and distribute and not new_st: 667 new_st = False 668 distribute = True 669 if not has_distribute and not has_new_st and not distribute and not new_st: 670 new_st = True 671 distribute = False 672 673 if new_st and distribute: 674 distribute = False 675 if new_st: 676 distribute = False 677 LOG.warning("Forcing to use setuptools as we have setuptools >= 0.7") 678 if distribute: 679 new_st = False 680 if buildout_ver == 1: 681 LOG.warning("Using distribute !") 682 bootstrap_args += " --distribute" 683 if not os.path.isdir(dbuild): 684 os.makedirs(dbuild) 685 upgrade_bootstrap(directory, offline=offline, buildout_ver=buildout_ver) 686 # be sure which buildout bootstrap we have 687 b_py = os.path.join(directory, "bootstrap.py") 688 with salt.utils.files.fopen(b_py) as fic: 689 content = salt.utils.stringutils.to_unicode(fic.read()) 690 if (test_release is not False) and " --accept-buildout-test-releases" in content: 691 bootstrap_args += " --accept-buildout-test-releases" 692 if config and '"-c"' in content: 693 bootstrap_args += " -c {}".format(config) 694 # be sure that the bootstrap belongs to the running user 695 try: 696 if runas: 697 uid = __salt__["user.info"](runas)["uid"] 698 gid = __salt__["user.info"](runas)["gid"] 699 os.chown("bootstrap.py", uid, gid) 700 except OSError as exc: 701 # don't block here, try to execute it if can pass 702 _logger.error( 703 "BUILDOUT bootstrap permissions error: %s", 704 exc, 705 exc_info=_logger.isEnabledFor(logging.DEBUG), 706 ) 707 cmd = "{} bootstrap.py {}".format(python, bootstrap_args) 708 ret = _Popen( 709 cmd, directory=directory, runas=runas, loglevel=loglevel, env=env, use_vt=use_vt 710 ) 711 output = ret["output"] 712 return {"comment": cmd, "out": output} 713 714 715@_salt_callback 716def run_buildout( 717 directory=".", 718 config="buildout.cfg", 719 parts=None, 720 onlyif=None, 721 unless=None, 722 offline=False, 723 newest=True, 724 runas=None, 725 env=(), 726 verbose=False, 727 debug=False, 728 use_vt=False, 729 loglevel=None, 730): 731 """ 732 Run a buildout in a directory. 733 734 directory 735 directory to execute in 736 737 config 738 alternative buildout configuration file to use 739 740 offline 741 are we executing buildout in offline mode 742 743 runas 744 user used to run buildout as 745 746 env 747 environment variables to set when running 748 749 onlyif 750 Only execute cmd if statement on the host return 0 751 752 unless 753 Do not execute cmd if statement on the host return 0 754 755 newest 756 run buildout in newest mode 757 758 force 759 run buildout unconditionally 760 761 verbose 762 run buildout in verbose mode (-vvvvv) 763 764 use_vt 765 Use the new salt VT to stream output [experimental] 766 767 CLI Example: 768 769 .. code-block:: bash 770 771 salt '*' buildout.run_buildout /srv/mybuildout 772 """ 773 directory = os.path.abspath(directory) 774 bcmd = os.path.join(directory, "bin", "buildout") 775 installed_cfg = os.path.join(directory, ".installed.cfg") 776 argv = [] 777 if verbose: 778 LOG.debug("Buildout is running in verbose mode!") 779 argv.append("-vvvvvvv") 780 if not newest and os.path.exists(installed_cfg): 781 LOG.debug("Buildout is running in non newest mode!") 782 argv.append("-N") 783 if newest: 784 LOG.debug("Buildout is running in newest mode!") 785 argv.append("-n") 786 if offline: 787 LOG.debug("Buildout is running in offline mode!") 788 argv.append("-o") 789 if debug: 790 LOG.debug("Buildout is running in debug mode!") 791 argv.append("-D") 792 cmds, outputs = [], [] 793 if parts: 794 for part in parts: 795 LOG.info( 796 "Installing single part: {}".format(part) 797 ) # pylint: disable=str-format-in-logging 798 cmd = "{} -c {} {} install {}".format(bcmd, config, " ".join(argv), part) 799 cmds.append(cmd) 800 outputs.append( 801 _Popen( 802 cmd, 803 directory=directory, 804 runas=runas, 805 env=env, 806 output=True, 807 loglevel=loglevel, 808 use_vt=use_vt, 809 ) 810 ) 811 else: 812 LOG.info("Installing all buildout parts") 813 cmd = "{} -c {} {}".format(bcmd, config, " ".join(argv)) 814 cmds.append(cmd) 815 outputs.append( 816 _Popen( 817 cmd, 818 directory=directory, 819 runas=runas, 820 loglevel=loglevel, 821 env=env, 822 output=True, 823 use_vt=use_vt, 824 ) 825 ) 826 827 return {"comment": "\n".join(cmds), "out": "\n".join(outputs)} 828 829 830def _merge_statuses(statuses): 831 status = BASE_STATUS.copy() 832 status["status"] = None 833 status["merged_statuses"] = True 834 status["out"] = "" 835 for st in statuses: 836 if status["status"] is not False: 837 status["status"] = st["status"] 838 out = st["out"] 839 comment = salt.utils.stringutils.to_unicode(st["comment"]) 840 logs = st["logs"] 841 logs_by_level = st["logs_by_level"] 842 outlog_by_level = st["outlog_by_level"] 843 outlog = st["outlog"] 844 if out: 845 if not status["out"]: 846 status["out"] = "" 847 status["out"] += "\n" 848 status["out"] += HR 849 out = salt.utils.stringutils.to_unicode(out) 850 status["out"] += "{}\n".format(out) 851 status["out"] += HR 852 if comment: 853 if not status["comment"]: 854 status["comment"] = "" 855 status["comment"] += "\n{}\n".format( 856 salt.utils.stringutils.to_unicode(comment) 857 ) 858 if outlog: 859 if not status["outlog"]: 860 status["outlog"] = "" 861 outlog = salt.utils.stringutils.to_unicode(outlog) 862 status["outlog"] += "\n{}".format(HR) 863 status["outlog"] += outlog 864 if outlog_by_level: 865 if not status["outlog_by_level"]: 866 status["outlog_by_level"] = "" 867 status["outlog_by_level"] += "\n{}".format(HR) 868 status["outlog_by_level"] += salt.utils.stringutils.to_unicode( 869 outlog_by_level 870 ) 871 status["logs"].extend( 872 [(a[0], salt.utils.stringutils.to_unicode(a[1])) for a in logs] 873 ) 874 for log in logs_by_level: 875 if log not in status["logs_by_level"]: 876 status["logs_by_level"][log] = [] 877 status["logs_by_level"][log].extend( 878 [salt.utils.stringutils.to_unicode(a) for a in logs_by_level[log]] 879 ) 880 return _encode_status(status) 881 882 883@_salt_callback 884def buildout( 885 directory=".", 886 config="buildout.cfg", 887 parts=None, 888 runas=None, 889 env=(), 890 buildout_ver=None, 891 test_release=False, 892 distribute=None, 893 new_st=None, 894 offline=False, 895 newest=False, 896 python=sys.executable, 897 debug=False, 898 verbose=False, 899 onlyif=None, 900 unless=None, 901 use_vt=False, 902 loglevel=None, 903): 904 """ 905 Run buildout in a directory. 906 907 directory 908 directory to execute in 909 910 config 911 buildout config to use 912 913 parts 914 specific buildout parts to run 915 916 runas 917 user used to run buildout as 918 919 env 920 environment variables to set when running 921 922 buildout_ver 923 force a specific buildout version (1 | 2) 924 925 test_release 926 buildout accept test release 927 928 new_st 929 Forcing use of setuptools >= 0.7 930 931 distribute 932 use distribute over setuptools if possible 933 934 offline 935 does buildout run offline 936 937 python 938 python to use 939 940 debug 941 run buildout with -D debug flag 942 943 onlyif 944 Only execute cmd if statement on the host return 0 945 946 unless 947 Do not execute cmd if statement on the host return 0 948 newest 949 run buildout in newest mode 950 951 verbose 952 run buildout in verbose mode (-vvvvv) 953 954 use_vt 955 Use the new salt VT to stream output [experimental] 956 957 CLI Example: 958 959 .. code-block:: bash 960 961 salt '*' buildout.buildout /srv/mybuildout 962 """ 963 LOG.info( 964 "Running buildout in {} ({})".format(directory, config) 965 ) # pylint: disable=str-format-in-logging 966 boot_ret = bootstrap( 967 directory, 968 config=config, 969 buildout_ver=buildout_ver, 970 test_release=test_release, 971 offline=offline, 972 new_st=new_st, 973 env=env, 974 runas=runas, 975 distribute=distribute, 976 python=python, 977 use_vt=use_vt, 978 loglevel=loglevel, 979 ) 980 buildout_ret = run_buildout( 981 directory=directory, 982 config=config, 983 parts=parts, 984 offline=offline, 985 newest=newest, 986 runas=runas, 987 env=env, 988 verbose=verbose, 989 debug=debug, 990 use_vt=use_vt, 991 loglevel=loglevel, 992 ) 993 # signal the decorator or our return 994 return _merge_statuses([boot_ret, buildout_ret]) 995 996 997def _check_onlyif_unless(onlyif, unless, directory, runas=None, env=()): 998 ret = None 999 status = BASE_STATUS.copy() 1000 if os.path.exists(directory): 1001 directory = os.path.abspath(directory) 1002 status["status"] = False 1003 retcode = __salt__["cmd.retcode"] 1004 if onlyif is not None: 1005 if not isinstance(onlyif, str): 1006 if not onlyif: 1007 _valid(status, "onlyif condition is false") 1008 elif isinstance(onlyif, str): 1009 if retcode(onlyif, cwd=directory, runas=runas, env=env) != 0: 1010 _valid(status, "onlyif condition is false") 1011 if unless is not None: 1012 if not isinstance(unless, str): 1013 if unless: 1014 _valid(status, "unless condition is true") 1015 elif isinstance(unless, str): 1016 if ( 1017 retcode( 1018 unless, cwd=directory, runas=runas, env=env, python_shell=False 1019 ) 1020 == 0 1021 ): 1022 _valid(status, "unless condition is true") 1023 if status["status"]: 1024 ret = status 1025 return ret 1026 1027 1028# vim:set et sts=4 ts=4 tw=80: 1029