File: //opt/saltstack/salt/lib/python3.10/site-packages/salt/states/virtualenv_mod.py
"""
Setup of Python virtualenv sandboxes.
.. versionadded:: 0.17.0
"""
import logging
import os
import salt.utils.functools
import salt.utils.platform
import salt.utils.versions
import salt.version
from salt.exceptions import CommandExecutionError, CommandNotFoundError
log = logging.getLogger(__name__)
# Define the module's virtual name
__virtualname__ = "virtualenv"
def __virtual__():
if "virtualenv.create" in __salt__:
return __virtualname__
return (False, "virtualenv module could not be loaded")
def managed(
name,
venv_bin=None,
requirements=None,
system_site_packages=False,
distribute=False,
use_wheel=False,
clear=False,
python=None,
extra_search_dir=None,
never_download=None,
prompt=None,
user=None,
cwd=None,
index_url=None,
extra_index_url=None,
pre_releases=False,
no_deps=False,
pip_download=None,
pip_download_cache=None,
pip_exists_action=None,
pip_ignore_installed=False,
proxy=None,
use_vt=False,
env_vars=None,
no_use_wheel=False,
pip_upgrade=False,
pip_pkgs=None,
pip_no_cache_dir=False,
pip_cache_dir=None,
process_dependency_links=False,
no_binary=None,
**kwargs,
):
"""
Create a virtualenv and optionally manage it with pip
name
Path to the virtualenv.
venv_bin: virtualenv
The name (and optionally path) of the virtualenv command. This can also
be set globally in the minion config file as ``virtualenv.venv_bin``.
requirements: None
Path to a pip requirements file. If the path begins with ``salt://``
the file will be transferred from the master file server.
use_wheel: False
Prefer wheel archives (requires pip >= 1.4).
python: None
Python executable used to build the virtualenv. When Salt is installed
from a onedir package. You will likely want to specify which python
interperter should be used.
user: None
The user under which to run virtualenv and pip.
cwd: None
Path to the working directory where `pip install` is executed.
no_deps: False
Pass `--no-deps` to `pip install`.
pip_exists_action: None
Default action of pip when a path already exists: (s)witch, (i)gnore,
(w)ipe, (b)ackup.
proxy: None
Proxy address which is passed to `pip install`.
env_vars: None
Set environment variables that some builds will depend on. For example,
a Python C-module may have a Makefile that needs INCLUDE_PATH set to
pick up a header file while compiling.
no_use_wheel: False
Force to not use wheel archives (requires pip>=1.4)
no_binary
Force to not use binary packages (requires pip >= 7.0.0)
Accepts either :all: to disable all binary packages, :none: to empty the set,
or a list of one or more packages
pip_upgrade: False
Pass `--upgrade` to `pip install`.
pip_pkgs: None
As an alternative to `requirements`, pass a list of pip packages that
should be installed.
process_dependency_links: False
Run pip install with the --process_dependency_links flag.
.. versionadded:: 2017.7.0
Also accepts any kwargs that the virtualenv module will. However, some
kwargs, such as the ``pip`` option, require ``- distribute: True``.
.. code-block:: yaml
/var/www/myvirtualenv.com:
virtualenv.managed:
- system_site_packages: False
- requirements: salt://REQUIREMENTS.txt
- env_vars:
PATH_VAR: '/usr/local/bin/'
Current versions of Salt use onedir packages and will use onedir python
interpreter by default. If you've installed Salt via out package
repository. You will likely want to provide the path to the interpreter
with which you would like to be used to create the virtual environment. The
interpreter can be specified by providing the `python` option.
"""
ret = {"name": name, "result": True, "comment": "", "changes": {}}
if "virtualenv.create" not in __salt__:
ret["result"] = False
ret["comment"] = "Virtualenv was not detected on this system"
return ret
if salt.utils.platform.is_windows():
venv_py = os.path.join(name, "Scripts", "python.exe")
else:
venv_py = os.path.join(name, "bin", "python")
venv_exists = os.path.exists(venv_py)
# Bail out early if the specified requirements file can't be found
if requirements and requirements.startswith("salt://"):
cached_requirements = __salt__["cp.is_cached"](requirements, __env__)
if not cached_requirements:
# It's not cached, let's cache it.
cached_requirements = __salt__["cp.cache_file"](requirements, __env__)
# Check if the master version has changed.
if cached_requirements and __salt__["cp.hash_file"](
requirements, __env__
) != __salt__["cp.hash_file"](cached_requirements, __env__):
cached_requirements = __salt__["cp.cache_file"](requirements, __env__)
if not cached_requirements:
ret.update(
{
"result": False,
"comment": "pip requirements file '{}' not found".format(
requirements
),
}
)
return ret
requirements = cached_requirements
# If it already exists, grab the version for posterity
if venv_exists and clear:
ret["changes"]["cleared_packages"] = __salt__["pip.freeze"](bin_env=name)
ret["changes"]["old"] = __salt__["cmd.run_stderr"](f"{venv_py} -V").strip("\n")
# Create (or clear) the virtualenv
if __opts__["test"]:
if venv_exists and clear:
ret["result"] = None
ret["comment"] = f"Virtualenv {name} is set to be cleared"
return ret
if venv_exists and not clear:
ret["comment"] = f"Virtualenv {name} is already created"
return ret
ret["result"] = None
ret["comment"] = f"Virtualenv {name} is set to be created"
return ret
if not venv_exists or (venv_exists and clear):
try:
venv_ret = __salt__["virtualenv.create"](
name,
venv_bin=venv_bin,
system_site_packages=system_site_packages,
distribute=distribute,
clear=clear,
python=python,
extra_search_dir=extra_search_dir,
never_download=never_download,
prompt=prompt,
user=user,
use_vt=use_vt,
**kwargs,
)
except CommandNotFoundError as err:
ret["result"] = False
ret["comment"] = f"Failed to create virtualenv: {err}"
return ret
if venv_ret["retcode"] != 0:
ret["result"] = False
ret["comment"] = venv_ret["stdout"] + venv_ret["stderr"]
return ret
ret["result"] = True
ret["changes"]["new"] = __salt__["cmd.run_stderr"](f"{venv_py} -V").strip("\n")
if clear:
ret["comment"] = "Cleared existing virtualenv"
else:
ret["comment"] = "Created new virtualenv"
elif venv_exists:
ret["comment"] = "virtualenv exists"
# Check that the pip binary supports the 'use_wheel' option
if use_wheel:
min_version = "1.4"
max_version = "9.0.3"
cur_version = __salt__["pip.version"](bin_env=name)
too_low = salt.utils.versions.compare(
ver1=cur_version, oper="<", ver2=min_version
)
too_high = salt.utils.versions.compare(
ver1=cur_version, oper=">", ver2=max_version
)
if too_low or too_high:
ret["result"] = False
ret["comment"] = (
"The 'use_wheel' option is only supported in "
"pip between {} and {}. The version of pip detected "
"was {}.".format(min_version, max_version, cur_version)
)
return ret
# Check that the pip binary supports the 'no_use_wheel' option
if no_use_wheel:
min_version = "1.4"
max_version = "9.0.3"
cur_version = __salt__["pip.version"](bin_env=name)
too_low = salt.utils.versions.compare(
ver1=cur_version, oper="<", ver2=min_version
)
too_high = salt.utils.versions.compare(
ver1=cur_version, oper=">", ver2=max_version
)
if too_low or too_high:
ret["result"] = False
ret["comment"] = (
"The 'no_use_wheel' option is only supported in "
"pip between {} and {}. The version of pip detected "
"was {}.".format(min_version, max_version, cur_version)
)
return ret
# Check that the pip binary supports the 'no_binary' option
if no_binary:
min_version = "7.0.0"
cur_version = __salt__["pip.version"](bin_env=name)
too_low = salt.utils.versions.compare(
ver1=cur_version, oper="<", ver2=min_version
)
if too_low:
ret["result"] = False
ret["comment"] = (
"The 'no_binary' option is only supported in "
"pip {} and newer. The version of pip detected "
"was {}.".format(min_version, cur_version)
)
return ret
# Populate the venv via a requirements file
if requirements or pip_pkgs:
try:
before = set(__salt__["pip.freeze"](bin_env=name, user=user, use_vt=use_vt))
except CommandExecutionError as exc:
ret["result"] = False
ret["comment"] = exc.strerror
return ret
if requirements:
if isinstance(requirements, str):
req_canary = requirements.split(",")[0]
elif isinstance(requirements, list):
req_canary = requirements[0]
else:
raise TypeError("pip requirements must be either a string or a list")
if req_canary != os.path.abspath(req_canary):
cwd = os.path.dirname(os.path.abspath(req_canary))
pip_ret = __salt__["pip.install"](
pkgs=pip_pkgs,
requirements=requirements,
process_dependency_links=process_dependency_links,
bin_env=name,
use_wheel=use_wheel,
no_use_wheel=no_use_wheel,
no_binary=no_binary,
user=user,
cwd=cwd,
index_url=index_url,
extra_index_url=extra_index_url,
download=pip_download,
download_cache=pip_download_cache,
pre_releases=pre_releases,
exists_action=pip_exists_action,
ignore_installed=pip_ignore_installed,
upgrade=pip_upgrade,
no_deps=no_deps,
proxy=proxy,
use_vt=use_vt,
env_vars=env_vars,
no_cache_dir=pip_no_cache_dir,
cache_dir=pip_cache_dir,
**kwargs,
)
ret["result"] &= pip_ret["retcode"] == 0
if pip_ret["retcode"] > 0:
ret["comment"] = "{}\n{}\n{}".format(
ret["comment"], pip_ret["stdout"], pip_ret["stderr"]
)
after = set(__salt__["pip.freeze"](bin_env=name))
new = list(after - before)
old = list(before - after)
if new or old:
ret["changes"]["packages"] = {
"new": new if new else "",
"old": old if old else "",
}
return ret
manage = salt.utils.functools.alias_function(managed, "manage")