Merge pull request #927 from fxdgear/develop

Major refactor of pip module, adding pip state module, changing virtualenv state to default no-site-packages to True
This commit is contained in:
Seth House 2012-03-17 21:47:13 -07:00
commit 1f6a076a2c
4 changed files with 485 additions and 41 deletions

View File

@ -1,68 +1,374 @@
'''
Install Python packages with pip to either the system or a virtualenv
'''
__opts__ = {
'pip_bin': 'pip',
}
import os
def _get_pip_bin(pip, env):
def _get_pip_bin(bin_env):
'''
Return the pip command to call, either from a virtualenv, an argument
passed in, or from the global modules options
'''
if env:
return os.path.join(env, 'bin', 'pip')
if not bin_env:
pip_bin = 'pip'
else:
return pip if pip else __opts__['pip_bin']
# try to get pip bin from env
if os.path.exists(os.path.join(bin_env, 'bin', 'pip')):
pip_bin = os.path.join(bin_env, 'bin', 'pip')
else:
pip_bin = bin_env
return pip_bin
def install(env='', requirements='', pkgs='', pip_bin=''):
def install(pkgs=None,
requirements=None,
env=None,
bin_env=None,
log=None,
proxy=None,
timeout=None,
editable=None,
find_links=None,
index_url=None,
extra_index_url=None,
no_index=False,
mirrors=None,
build=None,
target=None,
download=None,
download_cache=None,
source=None,
upgrade=False,
force_reinstall=False,
ignore_installed=False,
no_deps=False,
no_install=False,
no_download=False,
install_options=None):
'''
Install packages with pip
Install packages individually or from a pip requirements file. Install
packages globally or to a virtualenv.
env : None
The path to a virtualenv that pip should install to. This option takes
precendence over the ``pip_bin`` argument.
requirements : None
The path to a pip requirements file to install from
pkgs : None
A list of space-separated packages to install
pip_bin : 'pip'
The name (and optionally path) of the pip command to call. This option
will be ignored if the ``env`` argument is given since it will default
to the pip that is installed in the virtualenv. This option can also be
set in the minion config file as ``pip.pip_bin``.
pkgs
comma separated list of packages to install
requirements
path to requirements
bin_env
path to pip bin or path to virtualenv. If doing a system install,
and want to use a specific pip bin (pip-2.7, pip-2.6, etc..) just
specify the pip bin you want.
If installing into a virtualenv, just use the path to the virtualenv
(/home/code/path/to/virtualenv/)
env
depreicated, use bin_env now
log
Log file where a complete (maximum verbosity) record will be kept
proxy
Specify a proxy in the form
user:passwd@proxy.server:port. Note that the
user:password@ is optional and required only if you
are behind an authenticated proxy. If you provide
user@proxy.server:port then you will be prompted for a
password.
timeout
Set the socket timeout (default 15 seconds)
editable
install something editable(ie git+https://github.com/worldcompany/djangoembed.git#egg=djangoembed)
find_links
URL to look for packages at
index_url
Base URL of Python Package Index
extra_index_url
Extra URLs of package indexes to use in addition to ``index_url``
no_index
Ignore package index
mirrors
Specific mirror URLs to query (automatically adds --use-mirrors)
build
Unpack packages into ``build`` dir
target
Install packages into ``target`` dir
download
Download packages into ``download`` instead of installing them
download_cache
Cache downloaded packages in ``download_cache`` dir
source
Check out ``editable`` packages into ``source`` dir
upgrade
Upgrade all packages to the newest available version
force_reinstall
When upgrading, reinstall all packages even if they are already up-to-date.
ignore_installed
Ignore the installed packages (reinstalling instead)
no_deps
Ignore package dependencies
no_install
Download and unpack all packages, but don't actually install them
no_download
Don't download any packages, just install the ones
already downloaded (completes an install run with
--no-install)
install_options
Extra arguments to be supplied to the setup.py install
command (use like --install-option="--install-
scripts=/usr/local/bin"). Use multiple --install-
option options to pass multiple options to setup.py
install. If you are using an option with a directory
path, be sure to use absolute path.
CLI Example::
salt '*' pip.install /var/www/myvirtualenv.com \\
/path/to/requirements.txt
salt '*' pip.install <package name>,<package2 name>
salt '*' pip.install requirements=/path/to/requirements.txt
salt '*' pip.install <package name> bin_env=/path/to/virtualenv
salt '*' pip.install <package name> bin_env=/path/to/pip_bin
Comlicated CLI example::
salt '*' pip.install markdown,django editable=git+https://github.com/worldcompany/djangoembed.git#egg=djangoembed upgrade=True no_deps=True
'''
cmd = '{pip_bin} install {reqs} {pkgs}'.format(
pip_bin=_get_pip_bin(pip_bin, env),
reqs='-r {0}'.format(requirements) if requirements else '',
pkgs=pkgs)
# Switching from using `pip_bin` and `env` to just `bin_env`
# cause using an env and a pip bin that's not in the env could
# be problematic.
# Still using the `env` variable, for backwards compatiblity sake
# but going fwd you should specify either a pip bin or an env with
# the `bin_env` argument and we'll take care of the rest.
if env and not bin_env:
bin_env = env
cmd = '{0} install'.format(_get_pip_bin(bin_env))
if pkgs:
pkg = pkgs.replace(",", " ")
cmd = '{cmd} {pkg} '.format(
cmd=cmd, pkg=pkg)
if requirements:
cmd = '{cmd} --requirements{requirements} '.format(
cmd=cmd, requirements=requirements)
if log:
try:
# TODO make this check if writeable
os.path.exists(log)
except IOError:
raise IOError("'%s' is not writeable" % log)
cmd = '{cmd} --{log} '.format(
cmd=cmd, log=log)
if proxy:
cmd = '{cmd} --proxy={proxy} '.format(
cmd=cmd, proxy=proxy)
if timeout:
try:
int(timeout)
except ValueError:
raise ValueError("'%s' is not a valid integer base 10.")
cmd = '{cmd} --timeout={timeout} '.format(
cmd=cmd, timeout=timeout)
if editable:
if editable.find('egg') == -1:
raise Exception('You must specify an egg for this editable')
cmd = '{cmd} --editable={editable} '.format(
cmd=cmd, editable=editable)
if find_links:
if not find_links.startswith("http://"):
raise Exception("'%s' must be a valid url" % find_links)
cmd = '{cmd} --find_links={find_links}'.format(
cmd=cmd, find_links=find_links)
if index_url:
if not index_url.startswith("http://"):
raise Exception("'%s' must be a valid url" % index_url)
cmd = '{cmd} --index_url={index_url} '.format(
cmd=cmd, index_url=index_url)
if extra_index_url:
if not extra_index_url.startswith("http://"):
raise Exception("'%s' must be a valid url" % extra_index_url)
cmd = '{cmd} --extra_index_url={extra_index_url} '.format(
cmd=cmd, extra_index_url=extra_index_url)
if no_index:
cmd = '{cmd} --no-index '.format(cmd=cmd)
if mirrors:
if not mirrors.startswith("http://"):
raise Exception("'%s' must be a valid url" % mirrors)
cmd = '{cmd} --use-mirrors --mirrors={mirrors} '.format(
cmd=cmd, mirrors=mirrors)
if build:
cmd = '{cmd} --build={build} '.format(
cmd=cmd, build=build)
if target:
cmd = '{cmd} --target={target} '.format(
cmd=cmd, target=target)
if download:
cmd = '{cmd} --download={download} '.format(
cmd=cmd, download=download)
if download_cache:
cmd = '{cmd} --download_cache={download_cache} '.format(
cmd=cmd, download_cache=download_cache)
if source:
cmd = '{cmd} --source={source} '.format(
cmd=cmd, source=source)
if upgrade:
cmd = '{cmd} --upgrade '.format(cmd=cmd)
if force_reinstall:
cmd = '{cmd} --force-reinstall '.format(cmd=cmd)
if ignore_installed:
cmd = '{cmd} --ignore-installed '.format(cmd=cmd)
if no_deps:
cmd = '{cmd} --no-deps '.format(cmd=cmd)
if no_install:
cmd = '{cmd} --no-install '.format(cmd=cmd)
if no_download:
cmd = '{cmd} --no-download '.format(cmd=cmd)
if install_options:
cmd = '{cmd} --install-options={install_options} '.format(
cmd=cmd, install_options=install_options)
return __salt__['cmd.run'](cmd)
def freeze(env='', pip_bin=''):
def uninstall(pkgs=None,
requirements=None,
bin_env=None,
log=None,
proxy=None,
timeout=None):
'''
Uninstall packages with pip
Uninstall packages individually or from a pip requirements file. Uninstall
packages globally or from a virtualenv.
pkgs
comma separated list of packages to install
requirements
path to requirements
bin_env
path to pip bin or path to virtualenv. If doing an uninstall from
the system python and want to use a specific pip bin (pip-2.7,
pip-2.6, etc..) just specify the pip bin you want.
If uninstalling from a virtualenv, just use the path to the virtualenv
(/home/code/path/to/virtualenv/)
log
Log file where a complete (maximum verbosity) record will be kept
proxy
Specify a proxy in the form
user:passwd@proxy.server:port. Note that the
user:password@ is optional and required only if you
are behind an authenticated proxy. If you provide
user@proxy.server:port then you will be prompted for a
password.
timeout
Set the socket timeout (default 15 seconds)
CLI Example::
salt '*' pip.uninstall <package name>,<package2 name>
salt '*' pip.uninstall requirements=/path/to/requirements.txt
salt '*' pip.uninstall <package name> bin_env=/path/to/virtualenv
salt '*' pip.uninstall <package name> bin_env=/path/to/pip_bin
'''
cmd = '{0} uninstall -y '.format(_get_pip_bin(bin_env))
if pkgs:
pkg = pkgs.replace(",", " ")
cmd = '{cmd} {pkg} '.format(
cmd=cmd, pkg=pkg)
if requirements:
cmd = '{cmd} --requirements{requirements} '.format(
cmd=cmd, requirements=requirements)
if log:
try:
# TODO make this check if writeable
os.path.exists(log)
except IOError:
raise IOError("'%s' is not writeable" % log)
cmd = '{cmd} --{log} '.format(
cmd=cmd, log=log)
if proxy:
cmd = '{cmd} --proxy={proxy} '.format(
cmd=cmd, proxy=proxy)
if timeout:
try:
int(timeout)
except ValueError:
raise ValueError("'%s' is not a valid integer base 10.")
cmd = '{cmd} --timeout={timeout} '.format(
cmd=cmd, timeout=timeout)
return __salt__['cmd.run'](cmd).split('\n')
def freeze(bin_env=None):
'''
Return a list of installed packages either globally or in the specified
virtualenv
env : None
The path to a virtualenv that pip should install to. This option takes
precendence over the ``pip_bin`` argument.
pip_bin : 'pip'
The name (and optionally path) of the pip command to call. This option
will be ignored if the ``env`` argument is given since it will default
to the pip that is installed in the virtualenv. This option can also be
set in the minion config file as ``pip.pip_bin``.
bin_env
path to pip bin or path to virtualenv. If doing an uninstall from
the system python and want to use a specific pip bin (pip-2.7,
pip-2.6, etc..) just specify the pip bin you want.
If uninstalling from a virtualenv, just use the path to the virtualenv
(/home/code/path/to/virtualenv/)
'''
cmd = '{0} freeze'.format(_get_pip_bin(pip_bin, env))
cmd = '{0} freeze'.format(_get_pip_bin(bin_env))
return __salt__['cmd.run'](cmd).split('\n')
def list(prefix='', bin_env=None):
'''
Filter list of instaslled apps from ``freeze`` and check to see if ``prefix``
exists in the list of packages installed.
'''
packages = {}
cmd = '{0} freeze'.format(_get_pip_bin(bin_env))
for line in __salt__['cmd.run'](cmd).split("\n"):
if len(line.split("==")) >= 2:
name = line.split("==")[0]
version = line.split("==")[1]
if prefix:
if line.lower().startswith(prefix.lower()):
packages[name]=version
else:
packages[name]=version
return packages

View File

@ -11,7 +11,7 @@ __opts__ = {
def create(path,
venv_bin=__opts__['venv_bin'],
no_site_packages=True,
no_site_packages=False,
system_site_packages=False,
clear=False,
python='',

138
salt/states/pip.py Normal file
View File

@ -0,0 +1,138 @@
'''
Management of python packages
=============================
A state module to manage system installed python packages
.. code-block:: yaml
virtualenvwrapper:
pip:
- installed
- version: 3.0.1
'''
def installed(name,
pip_bin=None,
requirements=None,
env=None,
bin_env=None,
log=None,
proxy=None,
timeout=None,
editable=None,
find_links=None,
index_url=None,
extra_index_url=None,
no_index=False,
mirrors=None,
build=None,
target=None,
download=None,
download_cache=None,
source=None,
upgrade=False,
force_reinstall=False,
ignore_installed=False,
no_deps=False,
no_install=False,
no_download=False,
install_options=None):
'''
Make sure the package is installed
name
The name of the python package to install
pip_bin : None
Deprecated, use bin_env
env : None
Deprecated, use bin_env
bin_env : None
the pip executable or virtualenv to use
'''
if pip_bin and not bin_env:
bin_env = pip_bin
elif env and not bin_env:
bin_env = env
ret = {'name': name, 'result': None, 'comment': '', 'changes': {}}
if name in __salt__['pip.list'](name, bin_env):
ret['result'] = True
ret['comment'] = 'Package already installed'
return ret
if __salt__['pip.install'](pkgs=name,
requirements=requirements,
bin_env=bin_env,
log=log,
proxy=proxy,
timeout=timeout,
editable=editable,
find_links=find_links,
index_url=index_url,
extra_index_url=extra_index_url,
no_index=no_index,
mirrors=mirrors,
build=build,
target=target,
download=download,
download_cache=download_cache,
source=source,
upgrade=upgrade,
force_reinstall=force_reinstall,
ignore_installed=ignore_installed,
no_deps=no_deps,
no_install=no_install,
no_download=no_download,
install_options=install_options):
pkg_list = __salt__['pip.list'](name, bin_env)
version = pkg_list.values()[0]
pkg_name = pkg_list.keys()[0]
ret['result'] = True
ret['changes']["{0}=={1}".format(pkg_name, version)] = 'Installed'
ret['comment'] = 'Package was successfully installed'
else:
ret['result'] = False
ret['comment'] = 'Could not install package'
return ret
def removed(name,
packages=None,
requirements=None,
bin_env=None,
log=None,
proxy=None,
timeout=None):
"""
Make sure that a package is not installed.
name
The name of the package to uninstall
bin_env : None
the pip executable or virtualenenv to use
"""
ret = {'name': name, 'result': None, 'comment': '', 'changes': {}}
if name not in __salt__["pip.list"](packages=name, bin_env=bin_env):
ret["result"] = True
ret["comment"] = "Pacakge is not installed."
return ret
if __salt__["pip.uninstall"](packages=name,
requirements=requirements,
bin_env=bin_env,
log=log,
proxy=proxy,
timeout=timeout):
ret["result"] = True
ret["changes"][name] = "Removed"
ret["comment"] = "Package was successfully removed."
else:
ret["result"] = False
ret["comment"] = "Could not remove package."
return ret

View File

@ -62,7 +62,7 @@ def manage(name,
# If it already exists, grab the version for posterity
if venv_exists and clear:
ret['changes']['cleared_packages'] = __salt__['pip.freeze'](env=name)
ret['changes']['cleared_packages'] = __salt__['pip.freeze'](bin_env=name)
ret['changes']['old'] = __salt__['cmd.run_stderr'](
'{0} -V'.format(venv_py)).strip('\n')
@ -109,9 +109,9 @@ def manage(name,
else:
new_reqs = __salt__['cp.cache_local_file'](requirements)
before = set(__salt__['pip.freeze'](env=name))
__salt__['pip.install'](requirements=new_reqs, env=name)
after = set(__salt__['pip.freeze'](env=name))
before = set(__salt__['pip.freeze'](bin_env=name))
__salt__['pip.install'](requirements=new_reqs, bin_env=name)
after = set(__salt__['pip.freeze'](bin_env=name))
new = list(after - before)
old = list(before - after)