ansible/packaging/pip
Yeukhon Wong 2c72b905a7 Pip's name key can now accept remote package address.
Summary:

Pip module would abort when name is a remote package address because
the module was expecting a version if ``=`` is part of the name value.

Furthermore, the pip module would require either name or requirement to
be a key, although the documentation table said neither was required.
The fact that one of them must be present is not documented in the
documentation leads to confusion. This commit added this fact as part
of description.

In this commit, we resolve the confusion by stating either ``name``
or ``requirement`` is needed. Next, if the user puts remote address
as the value of the ``name`` key, we will not use mirror. Lastly,
if the user uses the remote serivice address as the name of the
package and the user does not supply -e option in extra_vars
(which is the whole point of this commit), we will add -e to
 extra_vars so pip command can run with -e option.
2013-05-09 05:11:02 -04:00

266 lines
9.6 KiB
Python

#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2012, Matt Wright <matt@nobien.net>
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
#
import tempfile
DOCUMENTATION = '''
---
module: pip
short_description: Manages Python library dependencies.
description:
- Manage Python library dependencies. To use this module, one of the following keys is required: C(name)
or C(requirements).
version_added: "0.7"
options:
name:
description:
- The name of a Python library to install or the url of the remote package.
required: false
default: null
version:
description:
- The version number to install of the Python library specified in the I(name) parameter
required: false
default: null
requirements:
description:
- The path to a pip requirements file
required: false
default: null
virtualenv:
description:
- An optional path to a I(virtualenv) directory to install into
required: false
default: null
virtualenv_site_packages:
version_added: "1.0"
description:
- Whether the virtual environment will inherit packages from the
global site-packages directory. Note that if this setting is
changed on an already existing virtual environment it will not
have any effect, the environment must be deleted and newly
created.
required: false
default: "no"
choices: [ "yes", "no" ]
virtualenv_command:
version_aded: "1.1"
description:
- The command to create the virtual environment with. For example
C(pyvenv), C(virtualenv), C(virtualenv2).
required: false
default: virtualenv
use_mirrors:
description:
- Whether to use mirrors when installing python libraries. If using
an older version of pip (< 1.0), you should set this to no because
older versions of pip do not support I(--use-mirrors).
required: false
default: "yes"
choices: [ "yes", "no" ]
version_added: "1.0"
state:
description:
- The state of module
required: false
default: present
choices: [ "present", "absent", "latest" ]
extra_args:
description:
- Extra arguments passed to pip.
required: false
default: null
version_added: "1.0"
examples:
- code: "pip: name=flask"
description: Install I(flask) python package.
- code: "pip: name=flask version=0.8"
description: Install I(flask) python package on version 0.8.
- code: "pip: name='svn+http://myrepo/svn/MyApp#egg=MyApp'"
description: Install I(MyApp) using one of the remote protocols (bzr,hg,git,svn) C(pip) supports. You do not have to supply '-e' option in extra_args.
- code: "pip: name=flask virtualenv=/my_app/venv"
description: "Install I(Flask) (U(http://flask.pocoo.org/)) into the specified I(virtualenv), inheriting none of the globally installed modules"
- code: "pip: name=flask virtualenv=/my_app/venv virtualenv_site_packages=yes"
description: "Install I(Flask) (U(http://flask.pocoo.org/)) into the specified I(virtualenv), inheriting globally installed modules"
- code: "pip: name=flask virtualenv=/my_app/venv virtualenv_command=virtualenv-2.7"
description: "Install I(Flask) (U(http://flask.pocoo.org/)) into the specified I(virtualenv), using Python 2.7"
- code: "pip: requirements=/my_app/requirements.txt"
description: Install specified python requirements.
- code: "pip: requirements=/my_app/requirements.txt virtualenv=/my_app/venv"
description: Install specified python requirements in indicated I(virtualenv).
- code: "pip: requirements=/my_app/requirements.txt extra_args='-i https://example.com/pypi/simple'"
description: Install specified python requirements and custom Index URL.
notes:
- Please note that virtualenv (U(http://www.virtualenv.org/)) must be installed on the remote host if the virtualenv parameter is specified.
requirements: [ "virtualenv", "pip" ]
author: Matt Wright
'''
def _get_full_name(name, version=None):
if version is None:
resp = name
else:
resp = name + '==' + version
return resp
def _get_pip(module, env):
# On Debian and Ubuntu, pip is pip.
# On Fedora18 and up, pip is python-pip.
# On Fedora17 and below, CentOS and RedHat 6 and 5, pip is pip-python.
# On Fedora, CentOS, and RedHat, the exception is in the virtualenv.
# There, pip is just pip.
# Try pip with the virtualenv directory first.
pip = module.get_bin_path('pip', False, ['%s/bin' % env])
for p in ['python-pip', 'pip-python']:
if not pip:
pip = module.get_bin_path(p, False, ['%s/bin' % env])
# pip should have been found by now. The final call to get_bin_path
# will trigger fail_json.
if not pip:
pip = module.get_bin_path('pip', True, ['%s/bin' % env])
return pip
def _fail(module, cmd, out, err):
msg = ''
if out:
msg += "stdout: %s" % (out, )
if err:
msg += "\n:stderr: %s" % (err, )
module.fail_json(cmd=cmd, msg=msg)
def main():
state_map = dict(
present='install',
absent='uninstall -y',
latest='install -U',
)
module = AnsibleModule(
argument_spec=dict(
state=dict(default='present', choices=state_map.keys()),
name=dict(default=None, required=False),
version=dict(default=None, required=False),
requirements=dict(default=None, required=False),
virtualenv=dict(default=None, required=False),
virtualenv_site_packages=dict(default='no', type='bool'),
virtualenv_command=dict(default='virtualenv', required=False),
use_mirrors=dict(default='yes', type='bool'),
extra_args=dict(default=None, required=False),
),
required_one_of=[['name', 'requirements']],
mutually_exclusive=[['name', 'requirements']],
supports_check_mode=True
)
state = module.params['state']
name = module.params['name']
version = module.params['version']
requirements = module.params['requirements']
use_mirrors = module.params['use_mirrors']
extra_args = module.params['extra_args']
if state == 'latest' and version is not None:
module.fail_json(msg='version is incompatible with state=latest')
# before we move on, check if name is #egg (remote repository/package)
# if it is, mark it as package so we can skip --use-mirrors and skip
# version arg checks
is_package = False
if '#egg=' in name:
is_package = True
else:
# we have to handle the case when version is required for non-remote package
if name and '=' in name:
module.fail_json(msg='version must be specified in the version parameter')
err = ''
out = ''
env = module.params['virtualenv']
virtualenv_command = module.params['virtualenv_command']
if env:
env = os.path.expanduser(env)
virtualenv = module.get_bin_path(virtualenv_command, True)
if not os.path.exists(os.path.join(env, 'bin', 'activate')):
if module.check_mode:
module.exit_json(changed=True)
if module.params['virtualenv_site_packages']:
cmd = '%s --system-site-packages %s' % (virtualenv, env)
else:
cmd = '%s %s' % (virtualenv, env)
rc, out_venv, err_venv = module.run_command(cmd)
out += out_venv
err += err_venv
if rc != 0:
_fail(module, cmd, out, err)
pip = _get_pip(module, env)
cmd = '%s %s' % (pip, state_map[state])
# If is_package is True, we must not add --use-mirrors option.
# Also, if -e option is not added by the user via extra_args,
# we must add -e ourselves.
if is_package:
args_list = [] # used if extra_args is not used at all
if extra_args:
args_list = extra_args.split(' ')
if '-e' not in args_list:
args_list.append('-e')
# Ok, we will reconstruct the option string
extra_args = ' '.join(args_list)
if not is_package or state != 'absent' and use_mirrors:
cmd += ' --use-mirrors'
if extra_args:
cmd += ' %s' % extra_args
if name:
cmd += ' %s' % _get_full_name(name, version)
elif requirements:
cmd += ' -r %s' % requirements
if module.check_mode:
module.exit_json(changed=True)
os.chdir(tempfile.gettempdir())
rc, out_pip, err_pip = module.run_command(cmd)
out += out_pip
err += err_pip
if rc == 1 and state == 'absent' and 'not installed' in out_pip:
pass # rc is 1 when attempting to uninstall non-installed package
elif rc != 0:
_fail(module, cmd, out, err)
if state == 'absent':
changed = 'Successfully uninstalled' in out_pip
else:
changed = 'Successfully installed' in out_pip
module.exit_json(changed=changed, cmd=cmd, name=name, version=version,
state=state, requirements=requirements, virtualenv=env, stdout=out, stderr=err)
# this is magic, see lib/ansible/module_common.py
#<<INCLUDE_ANSIBLE_MODULE_COMMON>>
main()