1""" 2Installation of Python Packages Using pip 3========================================= 4 5These states manage system installed python packages. Note that pip must be 6installed for these states to be available, so pip states should include a 7requisite to a pkg.installed state for the package which provides pip 8(``python-pip`` in most cases). Example: 9 10.. code-block:: yaml 11 12 python-pip: 13 pkg.installed 14 15 virtualenvwrapper: 16 pip.installed: 17 - require: 18 - pkg: python-pip 19""" 20 21 22import logging 23import re 24import sys 25import types 26 27import salt.utils.data 28import salt.utils.versions 29from salt.exceptions import CommandExecutionError, CommandNotFoundError 30 31try: 32 import pkg_resources 33 34 HAS_PKG_RESOURCES = True 35except ImportError: 36 HAS_PKG_RESOURCES = False 37 38 39# pylint: disable=import-error 40 41 42def purge_pip(): 43 """ 44 Purge pip and its sub-modules 45 """ 46 # Remove references to the loaded pip module above so reloading works 47 if "pip" not in sys.modules: 48 return 49 pip_related_entries = [ 50 (k, v) 51 for (k, v) in sys.modules.items() 52 if getattr(v, "__module__", "").startswith("pip.") 53 or (isinstance(v, types.ModuleType) and v.__name__.startswith("pip.")) 54 ] 55 for name, entry in pip_related_entries: 56 sys.modules.pop(name) 57 del entry 58 59 if "pip" in globals(): 60 del globals()["pip"] 61 if "pip" in locals(): 62 del locals()["pip"] 63 sys_modules_pip = sys.modules.pop("pip", None) 64 if sys_modules_pip is not None: 65 del sys_modules_pip 66 67 68def pip_has_internal_exceptions_mod(ver): 69 """ 70 True when the pip version has the `pip._internal.exceptions` module 71 """ 72 return salt.utils.versions.compare( 73 ver1=ver, 74 oper=">=", 75 ver2="10.0", 76 ) 77 78 79def pip_has_exceptions_mod(ver): 80 """ 81 True when the pip version has the `pip.exceptions` module 82 """ 83 if pip_has_internal_exceptions_mod(ver): 84 return False 85 return salt.utils.versions.compare(ver1=ver, oper=">=", ver2="1.0") 86 87 88try: 89 import pip 90 91 HAS_PIP = True 92except ImportError: 93 HAS_PIP = False 94 purge_pip() 95 96 97if HAS_PIP is True: 98 if not hasattr(purge_pip, "__pip_ver__"): 99 purge_pip.__pip_ver__ = pip.__version__ 100 elif purge_pip.__pip_ver__ != pip.__version__: 101 purge_pip() 102 import pip 103 104 purge_pip.__pip_ver__ = pip.__version__ 105 if salt.utils.versions.compare(ver1=pip.__version__, oper=">=", ver2="10.0"): 106 from pip._internal.exceptions import ( 107 InstallationError, 108 ) # pylint: disable=E0611,E0401 109 elif salt.utils.versions.compare(ver1=pip.__version__, oper=">=", ver2="1.0"): 110 from pip.exceptions import InstallationError # pylint: disable=E0611,E0401 111 else: 112 InstallationError = ValueError 113 114 115# pylint: enable=import-error 116 117log = logging.getLogger(__name__) 118 119# Define the module's virtual name 120__virtualname__ = "pip" 121 122 123def _from_line(*args, **kwargs): 124 import pip 125 126 if salt.utils.versions.compare(ver1=pip.__version__, oper=">=", ver2="18.1"): 127 import pip._internal.req.constructors # pylint: disable=E0611,E0401 128 129 return pip._internal.req.constructors.install_req_from_line(*args, **kwargs) 130 elif salt.utils.versions.compare(ver1=pip.__version__, oper=">=", ver2="10.0"): 131 import pip._internal.req # pylint: disable=E0611,E0401 132 133 return pip._internal.req.InstallRequirement.from_line(*args, **kwargs) 134 else: 135 import pip.req # pylint: disable=E0611,E0401 136 137 return pip.req.InstallRequirement.from_line(*args, **kwargs) 138 139 140def __virtual__(): 141 """ 142 Only load if the pip module is available in __salt__ 143 """ 144 if HAS_PKG_RESOURCES is False: 145 return False, "The pkg_resources python library is not installed" 146 if "pip.list" in __salt__: 147 return __virtualname__ 148 return False 149 150 151def _fulfills_version_spec(version, version_spec): 152 """ 153 Check version number against version specification info and return a 154 boolean value based on whether or not the version number meets the 155 specified version. 156 """ 157 for oper, spec in version_spec: 158 if oper is None: 159 continue 160 if not salt.utils.versions.compare( 161 ver1=version, oper=oper, ver2=spec, cmp_func=_pep440_version_cmp 162 ): 163 return False 164 return True 165 166 167def _check_pkg_version_format(pkg): 168 """ 169 Takes a package name and version specification (if any) and checks it using 170 the pip library. 171 """ 172 173 ret = {"result": False, "comment": None, "prefix": None, "version_spec": None} 174 175 if not HAS_PIP: 176 ret["comment"] = ( 177 "An importable Python 2 pip module is required but could not be " 178 "found on your system. This usually means that the system's pip " 179 "package is not installed properly." 180 ) 181 182 return ret 183 184 from_vcs = False 185 try: 186 # Get the requirement object from the pip library 187 try: 188 # With pip < 1.2, the __version__ attribute does not exist and 189 # vcs+URL urls are not properly parsed. 190 # The next line is meant to trigger an AttributeError and 191 # handle lower pip versions 192 log.debug("Installed pip version: %s", pip.__version__) 193 install_req = _from_line(pkg) 194 except AttributeError: 195 log.debug("Installed pip version is lower than 1.2") 196 supported_vcs = ("git", "svn", "hg", "bzr") 197 if pkg.startswith(supported_vcs): 198 for vcs in supported_vcs: 199 if pkg.startswith(vcs): 200 from_vcs = True 201 install_req = _from_line(pkg.split("{}+".format(vcs))[-1]) 202 break 203 else: 204 install_req = _from_line(pkg) 205 except (ValueError, InstallationError) as exc: 206 ret["result"] = False 207 if not from_vcs and "=" in pkg and "==" not in pkg: 208 ret["comment"] = ( 209 "Invalid version specification in package {}. '=' is " 210 "not supported, use '==' instead.".format(pkg) 211 ) 212 return ret 213 ret["comment"] = "pip raised an exception while parsing '{}': {}".format( 214 pkg, exc 215 ) 216 return ret 217 218 if install_req.req is None: 219 # This is most likely an url and there's no way to know what will 220 # be installed before actually installing it. 221 ret["result"] = True 222 ret["prefix"] = "" 223 ret["version_spec"] = [] 224 else: 225 ret["result"] = True 226 try: 227 ret["prefix"] = install_req.req.project_name 228 ret["version_spec"] = install_req.req.specs 229 except Exception: # pylint: disable=broad-except 230 ret["prefix"] = re.sub("[^A-Za-z0-9.]+", "-", install_req.name) 231 if hasattr(install_req, "specifier"): 232 specifier = install_req.specifier 233 else: 234 specifier = install_req.req.specifier 235 ret["version_spec"] = [(spec.operator, spec.version) for spec in specifier] 236 237 return ret 238 239 240def _check_if_installed( 241 prefix, 242 state_pkg_name, 243 version_spec, 244 ignore_installed, 245 force_reinstall, 246 upgrade, 247 user, 248 cwd, 249 bin_env, 250 env_vars, 251 index_url, 252 extra_index_url, 253 pip_list=False, 254 **kwargs 255): 256 """ 257 Takes a package name and version specification (if any) and checks it is 258 installed 259 260 Keyword arguments include: 261 pip_list: optional dict of installed pip packages, and their versions, 262 to search through to check if the package is installed. If not 263 provided, one will be generated in this function by querying the 264 system. 265 266 Returns: 267 result: None means the command failed to run 268 result: True means the package is installed 269 result: False means the package is not installed 270 """ 271 ret = {"result": False, "comment": None} 272 273 # If we are not passed a pip list, get one: 274 pip_list = salt.utils.data.CaseInsensitiveDict( 275 pip_list 276 or __salt__["pip.list"]( 277 prefix, bin_env=bin_env, user=user, cwd=cwd, env_vars=env_vars, **kwargs 278 ) 279 ) 280 281 # If the package was already installed, check 282 # the ignore_installed and force_reinstall flags 283 if ignore_installed is False and prefix in pip_list: 284 if force_reinstall is False and not upgrade: 285 # Check desired version (if any) against currently-installed 286 if ( 287 any(version_spec) 288 and _fulfills_version_spec(pip_list[prefix], version_spec) 289 ) or (not any(version_spec)): 290 ret["result"] = True 291 ret["comment"] = "Python package {} was already installed".format( 292 state_pkg_name 293 ) 294 return ret 295 if force_reinstall is False and upgrade: 296 # Check desired version (if any) against currently-installed 297 include_alpha = False 298 include_beta = False 299 include_rc = False 300 if any(version_spec): 301 for spec in version_spec: 302 if "a" in spec[1]: 303 include_alpha = True 304 if "b" in spec[1]: 305 include_beta = True 306 if "rc" in spec[1]: 307 include_rc = True 308 available_versions = __salt__["pip.list_all_versions"]( 309 prefix, 310 bin_env=bin_env, 311 include_alpha=include_alpha, 312 include_beta=include_beta, 313 include_rc=include_rc, 314 user=user, 315 cwd=cwd, 316 index_url=index_url, 317 extra_index_url=extra_index_url, 318 ) 319 desired_version = "" 320 if any(version_spec) and available_versions: 321 for version in reversed(available_versions): 322 if _fulfills_version_spec(version, version_spec): 323 desired_version = version 324 break 325 elif available_versions: 326 desired_version = available_versions[-1] 327 if not desired_version: 328 ret["result"] = True 329 ret["comment"] = ( 330 "Python package {} was already " 331 "installed and\nthe available upgrade " 332 "doesn't fulfills the version " 333 "requirements".format(prefix) 334 ) 335 return ret 336 if _pep440_version_cmp(pip_list[prefix], desired_version) == 0: 337 ret["result"] = True 338 ret["comment"] = "Python package {} was already installed".format( 339 state_pkg_name 340 ) 341 return ret 342 343 return ret 344 345 346def _pep440_version_cmp(pkg1, pkg2, ignore_epoch=False): 347 """ 348 Compares two version strings using pkg_resources.parse_version. 349 Return -1 if version1 < version2, 0 if version1 ==version2, 350 and 1 if version1 > version2. Return None if there was a problem 351 making the comparison. 352 """ 353 if HAS_PKG_RESOURCES is False: 354 log.warning( 355 "The pkg_resources packages was not loaded. Please install setuptools." 356 ) 357 return None 358 normalize = lambda x: str(x).split("!", 1)[-1] if ignore_epoch else str(x) 359 pkg1 = normalize(pkg1) 360 pkg2 = normalize(pkg2) 361 362 try: 363 if pkg_resources.parse_version(pkg1) < pkg_resources.parse_version(pkg2): 364 return -1 365 if pkg_resources.parse_version(pkg1) == pkg_resources.parse_version(pkg2): 366 return 0 367 if pkg_resources.parse_version(pkg1) > pkg_resources.parse_version(pkg2): 368 return 1 369 except Exception as exc: # pylint: disable=broad-except 370 log.exception(exc) 371 return None 372 373 374def installed( 375 name, 376 pkgs=None, 377 pip_bin=None, 378 requirements=None, 379 bin_env=None, 380 use_wheel=False, 381 no_use_wheel=False, 382 log=None, 383 proxy=None, 384 timeout=None, 385 repo=None, 386 editable=None, 387 find_links=None, 388 index_url=None, 389 extra_index_url=None, 390 no_index=False, 391 mirrors=None, 392 build=None, 393 target=None, 394 download=None, 395 download_cache=None, 396 source=None, 397 upgrade=False, 398 force_reinstall=False, 399 ignore_installed=False, 400 exists_action=None, 401 no_deps=False, 402 no_install=False, 403 no_download=False, 404 install_options=None, 405 global_options=None, 406 user=None, 407 cwd=None, 408 pre_releases=False, 409 cert=None, 410 allow_all_external=False, 411 allow_external=None, 412 allow_unverified=None, 413 process_dependency_links=False, 414 env_vars=None, 415 use_vt=False, 416 trusted_host=None, 417 no_cache_dir=False, 418 cache_dir=None, 419 no_binary=None, 420 extra_args=None, 421 **kwargs 422): 423 """ 424 Make sure the package is installed 425 426 name 427 The name of the python package to install. You can also specify version 428 numbers here using the standard operators ``==, >=, <=``. If 429 ``requirements`` is given, this parameter will be ignored. 430 431 Example: 432 433 .. code-block:: yaml 434 435 django: 436 pip.installed: 437 - name: django >= 1.6, <= 1.7 438 - require: 439 - pkg: python-pip 440 441 This will install the latest Django version greater than 1.6 but less 442 than 1.7. 443 444 requirements 445 Path to a pip requirements file. If the path begins with salt:// 446 the file will be transferred from the master file server. 447 448 user 449 The user under which to run pip 450 451 use_wheel : False 452 Prefer wheel archives (requires pip>=1.4) 453 454 no_use_wheel : False 455 Force to not use wheel archives (requires pip>=1.4) 456 457 no_binary 458 Force to not use binary packages (requires pip >= 7.0.0) 459 Accepts either :all: to disable all binary packages, :none: to empty the set, 460 or a list of one or more packages 461 462 Example: 463 464 .. code-block:: yaml 465 466 django: 467 pip.installed: 468 - no_binary: ':all:' 469 470 flask: 471 pip.installed: 472 - no_binary: 473 - itsdangerous 474 - click 475 476 log 477 Log file where a complete (maximum verbosity) record will be kept 478 479 proxy 480 Specify a proxy in the form 481 user:passwd@proxy.server:port. Note that the 482 user:password@ is optional and required only if you 483 are behind an authenticated proxy. If you provide 484 user@proxy.server:port then you will be prompted for a 485 password. 486 487 timeout 488 Set the socket timeout (default 15 seconds) 489 490 editable 491 install something editable (i.e. 492 git+https://github.com/worldcompany/djangoembed.git#egg=djangoembed) 493 494 find_links 495 URL to look for packages at 496 497 index_url 498 Base URL of Python Package Index 499 500 extra_index_url 501 Extra URLs of package indexes to use in addition to ``index_url`` 502 503 no_index 504 Ignore package index 505 506 mirrors 507 Specific mirror URL(s) to query (automatically adds --use-mirrors) 508 509 build 510 Unpack packages into ``build`` dir 511 512 target 513 Install packages into ``target`` dir 514 515 download 516 Download packages into ``download`` instead of installing them 517 518 download_cache 519 Cache downloaded packages in ``download_cache`` dir 520 521 source 522 Check out ``editable`` packages into ``source`` dir 523 524 upgrade 525 Upgrade all packages to the newest available version 526 527 force_reinstall 528 When upgrading, reinstall all packages even if they are already 529 up-to-date. 530 531 ignore_installed 532 Ignore the installed packages (reinstalling instead) 533 534 exists_action 535 Default action when a path already exists: (s)witch, (i)gnore, (w)ipe, 536 (b)ackup 537 538 no_deps 539 Ignore package dependencies 540 541 no_install 542 Download and unpack all packages, but don't actually install them 543 544 no_cache_dir: 545 Disable the cache. 546 547 cwd 548 Current working directory to run pip from 549 550 pre_releases 551 Include pre-releases in the available versions 552 553 cert 554 Provide a path to an alternate CA bundle 555 556 allow_all_external 557 Allow the installation of all externally hosted files 558 559 allow_external 560 Allow the installation of externally hosted files (comma separated list) 561 562 allow_unverified 563 Allow the installation of insecure and unverifiable files (comma separated list) 564 565 process_dependency_links 566 Enable the processing of dependency links 567 568 bin_env : None 569 Absolute path to a virtual environment directory or absolute path to 570 a pip executable. The example below assumes a virtual environment 571 has been created at ``/foo/.virtualenvs/bar``. 572 573 env_vars 574 Add or modify environment variables. Useful for tweaking build steps, 575 such as specifying INCLUDE or LIBRARY paths in Makefiles, build scripts or 576 compiler calls. This must be in the form of a dictionary or a mapping. 577 578 Example: 579 580 .. code-block:: yaml 581 582 django: 583 pip.installed: 584 - name: django_app 585 - env_vars: 586 CUSTOM_PATH: /opt/django_app 587 VERBOSE: True 588 589 use_vt 590 Use VT terminal emulation (see output while installing) 591 592 trusted_host 593 Mark this host as trusted, even though it does not have valid or any 594 HTTPS. 595 596 Example: 597 598 .. code-block:: yaml 599 600 django: 601 pip.installed: 602 - name: django >= 1.6, <= 1.7 603 - bin_env: /foo/.virtualenvs/bar 604 - require: 605 - pkg: python-pip 606 607 Or 608 609 Example: 610 611 .. code-block:: yaml 612 613 django: 614 pip.installed: 615 - name: django >= 1.6, <= 1.7 616 - bin_env: /foo/.virtualenvs/bar/bin/pip 617 - require: 618 - pkg: python-pip 619 620 .. admonition:: Attention 621 622 The following arguments are deprecated, do not use. 623 624 pip_bin : None 625 Deprecated, use ``bin_env`` 626 627 .. versionchanged:: 0.17.0 628 ``use_wheel`` option added. 629 630 install_options 631 632 Extra arguments to be supplied to the setup.py install command. 633 If you are using an option with a directory path, be sure to use 634 absolute path. 635 636 Example: 637 638 .. code-block:: yaml 639 640 django: 641 pip.installed: 642 - name: django 643 - install_options: 644 - --prefix=/blah 645 - require: 646 - pkg: python-pip 647 648 global_options 649 Extra global options to be supplied to the setup.py call before the 650 install command. 651 652 .. versionadded:: 2014.1.3 653 654 .. admonition:: Attention 655 656 As of Salt 0.17.0 the pip state **needs** an importable pip module. 657 This usually means having the system's pip package installed or running 658 Salt from an active `virtualenv`_. 659 660 The reason for this requirement is because ``pip`` already does a 661 pretty good job parsing its own requirements. It makes no sense for 662 Salt to do ``pip`` requirements parsing and validation before passing 663 them to the ``pip`` library. It's functionality duplication and it's 664 more error prone. 665 666 667 .. admonition:: Attention 668 669 Please set ``reload_modules: True`` to have the salt minion 670 import this module after installation. 671 672 673 Example: 674 675 .. code-block:: yaml 676 677 pyopenssl: 678 pip.installed: 679 - name: pyOpenSSL 680 - reload_modules: True 681 - exists_action: i 682 683 extra_args 684 pip keyword and positional arguments not yet implemented in salt 685 686 .. code-block:: yaml 687 688 pandas: 689 pip.installed: 690 - name: pandas 691 - extra_args: 692 - --latest-pip-kwarg: param 693 - --latest-pip-arg 694 695 .. warning:: 696 697 If unsupported options are passed here that are not supported in a 698 minion's version of pip, a `No such option error` will be thrown. 699 700 701 .. _`virtualenv`: http://www.virtualenv.org/en/latest/ 702 """ 703 if pip_bin and not bin_env: 704 bin_env = pip_bin 705 706 # If pkgs is present, ignore name 707 if pkgs: 708 if not isinstance(pkgs, list): 709 return { 710 "name": name, 711 "result": False, 712 "changes": {}, 713 "comment": "pkgs argument must be formatted as a list", 714 } 715 else: 716 pkgs = [name] 717 718 # Assumption: If `pkg` is not an `string`, it's a `collections.OrderedDict` 719 # prepro = lambda pkg: pkg if type(pkg) == str else \ 720 # ' '.join((pkg.items()[0][0], pkg.items()[0][1].replace(',', ';'))) 721 # pkgs = ','.join([prepro(pkg) for pkg in pkgs]) 722 prepro = ( 723 lambda pkg: pkg 724 if isinstance(pkg, str) 725 else " ".join((pkg.items()[0][0], pkg.items()[0][1])) 726 ) 727 pkgs = [prepro(pkg) for pkg in pkgs] 728 729 ret = {"name": ";".join(pkgs), "result": None, "comment": "", "changes": {}} 730 731 try: 732 cur_version = __salt__["pip.version"](bin_env) 733 except (CommandNotFoundError, CommandExecutionError) as err: 734 ret["result"] = None 735 ret["comment"] = "Error installing '{}': {}".format(name, err) 736 return ret 737 # Check that the pip binary supports the 'use_wheel' option 738 if use_wheel: 739 min_version = "1.4" 740 max_version = "9.0.3" 741 too_low = salt.utils.versions.compare( 742 ver1=cur_version, oper="<", ver2=min_version 743 ) 744 too_high = salt.utils.versions.compare( 745 ver1=cur_version, oper=">", ver2=max_version 746 ) 747 if too_low or too_high: 748 ret["result"] = False 749 ret["comment"] = ( 750 "The 'use_wheel' option is only supported in " 751 "pip between {} and {}. The version of pip detected " 752 "was {}.".format(min_version, max_version, cur_version) 753 ) 754 return ret 755 756 # Check that the pip binary supports the 'no_use_wheel' option 757 if no_use_wheel: 758 min_version = "1.4" 759 max_version = "9.0.3" 760 too_low = salt.utils.versions.compare( 761 ver1=cur_version, oper="<", ver2=min_version 762 ) 763 too_high = salt.utils.versions.compare( 764 ver1=cur_version, oper=">", ver2=max_version 765 ) 766 if too_low or too_high: 767 ret["result"] = False 768 ret["comment"] = ( 769 "The 'no_use_wheel' option is only supported in " 770 "pip between {} and {}. The version of pip detected " 771 "was {}.".format(min_version, max_version, cur_version) 772 ) 773 return ret 774 775 # Check that the pip binary supports the 'no_binary' option 776 if no_binary: 777 min_version = "7.0.0" 778 too_low = salt.utils.versions.compare( 779 ver1=cur_version, oper="<", ver2=min_version 780 ) 781 if too_low: 782 ret["result"] = False 783 ret["comment"] = ( 784 "The 'no_binary' option is only supported in " 785 "pip {} and newer. The version of pip detected " 786 "was {}.".format(min_version, cur_version) 787 ) 788 return ret 789 790 # Get the packages parsed name and version from the pip library. 791 # This only is done when there is no requirements or editable parameter. 792 pkgs_details = [] 793 if pkgs and not (requirements or editable): 794 comments = [] 795 for pkg in iter(pkgs): 796 out = _check_pkg_version_format(pkg) 797 if out["result"] is False: 798 ret["result"] = False 799 comments.append(out["comment"]) 800 elif out["result"] is True: 801 pkgs_details.append((out["prefix"], pkg, out["version_spec"])) 802 803 if ret["result"] is False: 804 ret["comment"] = "\n".join(comments) 805 return ret 806 807 # If a requirements file is specified, only install the contents of the 808 # requirements file. Similarly, using the --editable flag with pip should 809 # also ignore the "name" and "pkgs" parameters. 810 target_pkgs = [] 811 already_installed_comments = [] 812 if requirements or editable: 813 comments = [] 814 # Append comments if this is a dry run. 815 if __opts__["test"]: 816 ret["result"] = None 817 if requirements: 818 # TODO: Check requirements file against currently-installed 819 # packages to provide more accurate state output. 820 comments.append( 821 "Requirements file '{}' will be processed.".format(requirements) 822 ) 823 if editable: 824 comments.append( 825 "Package will be installed in editable mode (i.e. " 826 'setuptools "develop mode") from {}.'.format(editable) 827 ) 828 ret["comment"] = " ".join(comments) 829 return ret 830 831 # No requirements case. 832 # Check pre-existence of the requested packages. 833 else: 834 # Attempt to pre-cache a the current pip list 835 try: 836 pip_list = __salt__["pip.list"](bin_env=bin_env, user=user, cwd=cwd) 837 # If we fail, then just send False, and we'll try again in the next function call 838 except Exception as exc: # pylint: disable=broad-except 839 log.exception(exc) 840 pip_list = False 841 842 for prefix, state_pkg_name, version_spec in pkgs_details: 843 844 if prefix: 845 out = _check_if_installed( 846 prefix, 847 state_pkg_name, 848 version_spec, 849 ignore_installed, 850 force_reinstall, 851 upgrade, 852 user, 853 cwd, 854 bin_env, 855 env_vars, 856 index_url, 857 extra_index_url, 858 pip_list, 859 **kwargs 860 ) 861 # If _check_if_installed result is None, something went wrong with 862 # the command running. This way we keep stateful output. 863 if out["result"] is None: 864 ret["result"] = False 865 ret["comment"] = out["comment"] 866 return ret 867 else: 868 out = {"result": False, "comment": None} 869 870 result = out["result"] 871 872 # The package is not present. Add it to the pkgs to install. 873 if result is False: 874 # Replace commas (used for version ranges) with semicolons 875 # (which are not supported) in name so it does not treat 876 # them as multiple packages. 877 target_pkgs.append((prefix, state_pkg_name.replace(",", ";"))) 878 879 # Append comments if this is a dry run. 880 if __opts__["test"]: 881 # If there is more than one package, compute 882 # the total amount and exit 883 if len(pkgs_details) > 1: 884 msg = "Python package(s) set to be installed:" 885 for pkg in pkgs_details: 886 msg += "\n" 887 msg += pkg[1] 888 ret["comment"] = msg 889 else: 890 msg = "Python package {0} is set to be installed" 891 ret["comment"] = msg.format(state_pkg_name) 892 ret["result"] = None 893 return ret 894 895 # The package is already present and will not be reinstalled. 896 elif result is True: 897 # Append comment stating its presence 898 already_installed_comments.append(out["comment"]) 899 900 # The command pip.list failed. Abort. 901 elif result is None: 902 ret["result"] = None 903 ret["comment"] = out["comment"] 904 return ret 905 906 # No packages to install. 907 if not target_pkgs: 908 ret["result"] = True 909 aicomms = "\n".join(already_installed_comments) 910 last_line = "All specified packages are already installed" + ( 911 " and up-to-date" if upgrade else "" 912 ) 913 ret["comment"] = aicomms + ("\n" if aicomms else "") + last_line 914 return ret 915 916 # Construct the string that will get passed to the install call 917 pkgs_str = ",".join([state_name for _, state_name in target_pkgs]) 918 919 # Call to install the package. Actual installation takes place here 920 pip_install_call = __salt__["pip.install"]( 921 pkgs="{}".format(pkgs_str) if pkgs_str else "", 922 requirements=requirements, 923 bin_env=bin_env, 924 use_wheel=use_wheel, 925 no_use_wheel=no_use_wheel, 926 no_binary=no_binary, 927 log=log, 928 proxy=proxy, 929 timeout=timeout, 930 editable=editable, 931 find_links=find_links, 932 index_url=index_url, 933 extra_index_url=extra_index_url, 934 no_index=no_index, 935 mirrors=mirrors, 936 build=build, 937 target=target, 938 download=download, 939 download_cache=download_cache, 940 source=source, 941 upgrade=upgrade, 942 force_reinstall=force_reinstall, 943 ignore_installed=ignore_installed, 944 exists_action=exists_action, 945 no_deps=no_deps, 946 no_install=no_install, 947 no_download=no_download, 948 install_options=install_options, 949 global_options=global_options, 950 user=user, 951 cwd=cwd, 952 pre_releases=pre_releases, 953 cert=cert, 954 allow_all_external=allow_all_external, 955 allow_external=allow_external, 956 allow_unverified=allow_unverified, 957 process_dependency_links=process_dependency_links, 958 saltenv=__env__, 959 env_vars=env_vars, 960 use_vt=use_vt, 961 trusted_host=trusted_host, 962 no_cache_dir=no_cache_dir, 963 extra_args=extra_args, 964 disable_version_check=True, 965 **kwargs 966 ) 967 968 if pip_install_call and pip_install_call.get("retcode", 1) == 0: 969 ret["result"] = True 970 971 if requirements or editable: 972 comments = [] 973 if requirements: 974 PIP_REQUIREMENTS_NOCHANGE = [ 975 "Requirement already satisfied", 976 "Requirement already up-to-date", 977 "Requirement not upgraded", 978 "Collecting", 979 "Cloning", 980 "Cleaning up...", 981 "Looking in indexes", 982 ] 983 for line in pip_install_call.get("stdout", "").split("\n"): 984 if not any( 985 [line.strip().startswith(x) for x in PIP_REQUIREMENTS_NOCHANGE] 986 ): 987 ret["changes"]["requirements"] = True 988 if ret["changes"].get("requirements"): 989 comments.append( 990 "Successfully processed requirements file {}.".format( 991 requirements 992 ) 993 ) 994 else: 995 comments.append("Requirements were already installed.") 996 997 if editable: 998 comments.append( 999 "Package successfully installed from VCS checkout {}.".format( 1000 editable 1001 ) 1002 ) 1003 ret["changes"]["editable"] = True 1004 ret["comment"] = " ".join(comments) 1005 else: 1006 1007 # Check that the packages set to be installed were installed. 1008 # Create comments reporting success and failures 1009 pkg_404_comms = [] 1010 1011 already_installed_packages = set() 1012 for line in pip_install_call.get("stdout", "").split("\n"): 1013 # Output for already installed packages: 1014 # 'Requirement already up-to-date: jinja2 in /usr/local/lib/python2.7/dist-packages\nCleaning up...' 1015 if line.startswith("Requirement already up-to-date: "): 1016 package = line.split(":", 1)[1].split()[0] 1017 already_installed_packages.add(package.lower()) 1018 1019 for prefix, state_name in target_pkgs: 1020 1021 # Case for packages that are not an URL 1022 if prefix: 1023 pipsearch = salt.utils.data.CaseInsensitiveDict( 1024 __salt__["pip.list"]( 1025 prefix, 1026 bin_env, 1027 user=user, 1028 cwd=cwd, 1029 env_vars=env_vars, 1030 **kwargs 1031 ) 1032 ) 1033 1034 # If we didn't find the package in the system after 1035 # installing it report it 1036 if not pipsearch: 1037 pkg_404_comms.append( 1038 "There was no error installing package '{}' " 1039 "although it does not show when calling " 1040 "'pip.freeze'.".format(pkg) 1041 ) 1042 else: 1043 if ( 1044 prefix in pipsearch 1045 and prefix.lower() not in already_installed_packages 1046 ): 1047 ver = pipsearch[prefix] 1048 ret["changes"]["{}=={}".format(prefix, ver)] = "Installed" 1049 # Case for packages that are an URL 1050 else: 1051 ret["changes"]["{}==???".format(state_name)] = "Installed" 1052 1053 # Set comments 1054 aicomms = "\n".join(already_installed_comments) 1055 succ_comm = ( 1056 "All packages were successfully installed" 1057 if not pkg_404_comms 1058 else "\n".join(pkg_404_comms) 1059 ) 1060 ret["comment"] = aicomms + ("\n" if aicomms else "") + succ_comm 1061 1062 return ret 1063 1064 elif pip_install_call: 1065 ret["result"] = False 1066 if "stdout" in pip_install_call: 1067 error = "Error: {} {}".format( 1068 pip_install_call["stdout"], pip_install_call["stderr"] 1069 ) 1070 else: 1071 error = "Error: {}".format(pip_install_call["comment"]) 1072 1073 if requirements or editable: 1074 comments = [] 1075 if requirements: 1076 comments.append( 1077 'Unable to process requirements file "{}"'.format(requirements) 1078 ) 1079 if editable: 1080 comments.append( 1081 "Unable to install from VCS checkout {}.".format(editable) 1082 ) 1083 comments.append(error) 1084 ret["comment"] = " ".join(comments) 1085 else: 1086 pkgs_str = ", ".join([state_name for _, state_name in target_pkgs]) 1087 aicomms = "\n".join(already_installed_comments) 1088 error_comm = "Failed to install packages: {}. {}".format(pkgs_str, error) 1089 ret["comment"] = aicomms + ("\n" if aicomms else "") + error_comm 1090 else: 1091 ret["result"] = False 1092 ret["comment"] = "Could not install package" 1093 1094 return ret 1095 1096 1097def removed( 1098 name, 1099 requirements=None, 1100 bin_env=None, 1101 log=None, 1102 proxy=None, 1103 timeout=None, 1104 user=None, 1105 cwd=None, 1106 use_vt=False, 1107): 1108 """ 1109 Make sure that a package is not installed. 1110 1111 name 1112 The name of the package to uninstall 1113 user 1114 The user under which to run pip 1115 bin_env : None 1116 the pip executable or virtualenenv to use 1117 use_vt 1118 Use VT terminal emulation (see output while installing) 1119 """ 1120 ret = {"name": name, "result": None, "comment": "", "changes": {}} 1121 1122 try: 1123 pip_list = __salt__["pip.list"](bin_env=bin_env, user=user, cwd=cwd) 1124 except (CommandExecutionError, CommandNotFoundError) as err: 1125 ret["result"] = False 1126 ret["comment"] = "Error uninstalling '{}': {}".format(name, err) 1127 return ret 1128 1129 if name not in pip_list: 1130 ret["result"] = True 1131 ret["comment"] = "Package is not installed." 1132 return ret 1133 1134 if __opts__["test"]: 1135 ret["result"] = None 1136 ret["comment"] = "Package {} is set to be removed".format(name) 1137 return ret 1138 1139 if __salt__["pip.uninstall"]( 1140 pkgs=name, 1141 requirements=requirements, 1142 bin_env=bin_env, 1143 log=log, 1144 proxy=proxy, 1145 timeout=timeout, 1146 user=user, 1147 cwd=cwd, 1148 use_vt=use_vt, 1149 ): 1150 ret["result"] = True 1151 ret["changes"][name] = "Removed" 1152 ret["comment"] = "Package was successfully removed." 1153 else: 1154 ret["result"] = False 1155 ret["comment"] = "Could not remove package." 1156 return ret 1157 1158 1159def uptodate(name, bin_env=None, user=None, cwd=None, use_vt=False): 1160 """ 1161 .. versionadded:: 2015.5.0 1162 1163 Verify that the system is completely up to date. 1164 1165 name 1166 The name has no functional value and is only used as a tracking 1167 reference 1168 user 1169 The user under which to run pip 1170 bin_env 1171 the pip executable or virtualenenv to use 1172 use_vt 1173 Use VT terminal emulation (see output while installing) 1174 """ 1175 ret = {"name": name, "changes": {}, "result": False, "comment": "Failed to update."} 1176 1177 try: 1178 packages = __salt__["pip.list_upgrades"](bin_env=bin_env, user=user, cwd=cwd) 1179 except Exception as e: # pylint: disable=broad-except 1180 ret["comment"] = str(e) 1181 return ret 1182 1183 if not packages: 1184 ret["comment"] = "System is already up-to-date." 1185 ret["result"] = True 1186 return ret 1187 elif __opts__["test"]: 1188 ret["comment"] = "System update will be performed" 1189 ret["result"] = None 1190 return ret 1191 1192 updated = __salt__["pip.upgrade"]( 1193 bin_env=bin_env, user=user, cwd=cwd, use_vt=use_vt 1194 ) 1195 1196 if updated.get("result") is False: 1197 ret.update(updated) 1198 elif updated: 1199 ret["changes"] = updated 1200 ret["comment"] = "Upgrade successful." 1201 ret["result"] = True 1202 else: 1203 ret["comment"] = "Upgrade failed." 1204 1205 return ret 1206