pip module improvements

- Do not silently ignore malformed pip requirements files.
- Properly reports changed when removing packages.
- "latest" i.e. --upgrade is *not* incompatible with requirements files.
- Less branchy, simpler logic.
- Removed pointless variable "initializations", Python doesn't need that.
  Other code simplifications.
- Fun fact; pip install is (kind of) case insensitive, pip freeze is not.
  So, 'sqlalchemy' will be reported as installed by install, but missing
  by freeze.

The perhaps controversial change and the one that led to finding /
fixing above issues...

Instead of adding command parameters 'index', and 'find', and 'mirrors',
and etc.  Added 'extra_args' which are passed onto pip.

The use case for --index-url is having a private pypi repo, like
http://pypi.python.org/pypi/localshop, to which you publish private
packages.  I'm sure most every pip option has a use case for someone.
extra_args handles all those. Can reserve ansible command parameters for
the most common.

Tested with pip 1.1.
reviewable/pr18780/r1
Norman J. Harman Jr 12 years ago
parent 498f44f372
commit 900289f83d

214
pip

@ -62,23 +62,32 @@ options:
required: false required: false
default: present default: present
choices: [ "present", "absent", "latest" ] choices: [ "present", "absent", "latest" ]
extra_args:
description:
- Extra arguments passed to pip.
required: false
default: null
version_added: "1.0"
examples: examples:
- code: "pip: name=flask" - code: "pip: name=flask"
description: Install I(flask) python package. description: Install I(flask) python package.
- code: "pip: name=flask version=0.8" - code: "pip: name=flask version=0.8"
description: Install I(flask) python package on version 0.8. description: Install I(flask) python package on version 0.8.
- code: "pip: name=flask virtualenv=/srv/webapps/my_app/venv" - code: "pip: name=flask virtualenv=/my_app/venv"
description: "Install I(Flask) (U(http://flask.pocoo.org/)) into the specified I(virtualenv)" description: "Install I(Flask) (U(http://flask.pocoo.org/)) into the specified I(virtualenv)"
- code: "pip: requirements=/srv/webapps/my_app/src/requirements.txt" - code: "pip: requirements=/my_app/requirements.txt"
description: Install specified python requirements. description: Install specified python requirements.
- code: "pip: requirements=/srv/webapps/my_app/src/requirements.txt virtualenv=/srv/webapps/my_app/venv" - code: "pip: requirements=/my_app/requirements.txt virtualenv=/my_app/venv"
description: Install specified python requirements in indicated I(virtualenv). 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: notes:
- Please note that U(http://www.virtualenv.org/, virtualenv) must be installed on the remote host if the virtualenv parameter is specified. - Please note that U(http://www.virtualenv.org/, virtualenv) must be installed on the remote host if the virtualenv parameter is specified.
requirements: [ "virtualenv", "pip" ] requirements: [ "virtualenv", "pip" ]
author: Matt Wright author: Matt Wright
''' '''
def _get_full_name(name, version=None): def _get_full_name(name, version=None):
if version is None: if version is None:
resp = name resp = name
@ -86,28 +95,23 @@ def _get_full_name(name, version=None):
resp = name + '==' + version resp = name + '==' + version
return resp return resp
def _ensure_virtualenv(module, env, virtualenv):
if os.path.exists(os.path.join(env, 'bin', 'activate')):
return 0, '', ''
else:
return _run('%s %s' % (virtualenv, env))
def _is_package_installed(name, pip, version=None, requirements=None):
cmd = '%s freeze' % pip
if requirements is not None:
cmd += ' -r %s' % requirements
rc, status_stdout, status_stderr = _run(cmd)
if requirements is not None:
if 'not installed' in status_stderr:
return False
else:
return True
return _get_full_name(name, version).lower() in status_stdout.lower()
def _did_install(out): def _get_pip(module, env):
return 'Successfully installed' in out # 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 _run(cmd): def _run(cmd):
@ -118,128 +122,88 @@ def _run(cmd):
return (process.returncode, stdout, stderr) return (process.returncode, stdout, stderr)
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(): def main():
pip = None state_map = dict(
virtualenv = None present='install',
env = None absent='uninstall -y',
latest='install -U',
arg_spec = dict(
state=dict(default='present', choices=['absent', 'present', 'latest']),
name=dict(default=None, required=False),
version=dict(default=None, required=False),
requirements=dict(default=None, required=False),
virtualenv=dict(default=None, required=False),
use_mirrors=dict(default='yes', choices=BOOLEANS)
) )
module = AnsibleModule( module = AnsibleModule(
argument_spec=arg_spec, argument_spec=dict(
required_one_of=[['name','requirements']], state=dict(default='present', choices=state_map.keys()),
mutually_exclusive=[['name','requirements']], name=dict(default=None, required=False),
version=dict(default=None, required=False),
requirements=dict(default=None, required=False),
virtualenv=dict(default=None, required=False),
use_mirrors=dict(default='yes', choices=BOOLEANS),
extra_args=dict(default=None, required=False),
),
required_one_of=[['name', 'requirements']],
mutually_exclusive=[['name', 'requirements']],
) )
rc = 0
err = ''
out = ''
env = module.params['virtualenv']
if env:
virtualenv = module.get_bin_path('virtualenv', True)
rc_venv, out_venv, err_venv = _ensure_virtualenv(module, env, virtualenv)
rc += rc_venv
out += out_venv
err += err_venv
# 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])
state = module.params['state'] state = module.params['state']
name = module.params['name'] name = module.params['name']
version = module.params['version'] version = module.params['version']
requirements = module.params['requirements'] requirements = module.params['requirements']
use_mirrors = module.boolean(module.params['use_mirrors']) use_mirrors = module.boolean(module.params['use_mirrors'])
command_map = dict(present='install', absent='uninstall', latest='install') extra_args = module.params['extra_args']
if state == 'latest' and version is not None: if state == 'latest' and version is not None:
module.fail_json(msg='version is incompatible with state=latest') module.fail_json(msg='version is incompatible with state=latest')
if name and '=' in name:
module.fail_json(msg='version must be specified in the version parameter')
if state == 'latest' and requirements is not None: err = ''
module.fail_json(msg='requirements is incompatible with state=latest') out = ''
if name is not None and '=' in name:
module.fail_json(msg='versions must be specified in the version= parameter')
cmd = None
installed = None
if name and state == 'latest': env = module.params['virtualenv']
cmd = '%s %s %s --upgrade' % (pip, command_map[state], name) if env:
rc_pip, out_pip, err_pip = _run(cmd) virtualenv = module.get_bin_path('virtualenv', True)
if not os.path.exists(os.path.join(env, 'bin', 'activate')):
cmd = '%s %s' % (virtualenv, env)
rc, out_venv, err_venv = _run(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 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
rc += rc_pip rc, out_pip, err_pip = _run(cmd)
out += out_pip out += out_pip
err += err_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 changed = 'Successfully installed' in out_pip
elif name or requirements:
installed = _is_package_installed(name, pip, version, requirements)
changed = ((installed and state == 'absent') or
(not installed and state == 'present'))
if changed:
cmd = '%s %s ' % (pip, command_map[state])
if name:
if state == 'present':
full_name = _get_full_name(name, version)
else:
full_name = name
cmd += '%s' % full_name
elif requirements:
cmd += ' -r %s' % requirements
if state == 'absent':
cmd = cmd + ' -y'
elif use_mirrors:
cmd = cmd + ' --use-mirrors'
rc_pip, out_pip, err_pip = _run(cmd)
rc += rc_pip
out += out_pip
err += err_pip
if requirements:
changed = ((_did_install(out) and state == 'present') or
(not _did_install(out) and state == 'absent'))
if rc != 0:
if not out:
msg = err
elif not err:
msg = out
else:
msg = "stdout: %s\n:stderr: %s" % (out, err)
module.fail_json(msg=msg, cmd=cmd)
module.exit_json(changed=changed, cmd=cmd, name=name, version=version, module.exit_json(changed=changed, cmd=cmd, name=name, version=version,
state=state, requirements=requirements, virtualenv=env) state=state, requirements=requirements, virtualenv=env)

Loading…
Cancel
Save