mirror of
https://github.com/ansible-collections/community.general.git
synced 2025-06-28 03:00:23 -07:00
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.
This commit is contained in:
parent
52be556e1f
commit
19b84d0527
1 changed files with 91 additions and 127 deletions
218
library/pip
218
library/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 _get_pip(module, env):
|
||||||
def _is_package_installed(name, pip, version=None, requirements=None):
|
# On Debian and Ubuntu, pip is pip.
|
||||||
cmd = '%s freeze' % pip
|
# On Fedora18 and up, pip is python-pip.
|
||||||
if requirements is not None:
|
# On Fedora17 and below, CentOS and RedHat 6 and 5, pip is pip-python.
|
||||||
cmd += ' -r %s' % requirements
|
# On Fedora, CentOS, and RedHat, the exception is in the virtualenv.
|
||||||
rc, status_stdout, status_stderr = _run(cmd)
|
# There, pip is just pip.
|
||||||
if requirements is not None:
|
# Try pip with the virtualenv directory first.
|
||||||
if 'not installed' in status_stderr:
|
pip = module.get_bin_path('pip', False, ['%s/bin' % env])
|
||||||
return False
|
for p in ['python-pip', 'pip-python']:
|
||||||
else:
|
if not pip:
|
||||||
return True
|
pip = module.get_bin_path(p, False, ['%s/bin' % env])
|
||||||
return _get_full_name(name, version).lower() in status_stdout.lower()
|
# pip should have been found by now. The final call to get_bin_path
|
||||||
|
# will trigger fail_json.
|
||||||
|
if not pip:
|
||||||
def _did_install(out):
|
pip = module.get_bin_path('pip', True, ['%s/bin' % env])
|
||||||
return 'Successfully installed' in out
|
return pip
|
||||||
|
|
||||||
|
|
||||||
def _run(cmd):
|
def _run(cmd):
|
||||||
|
@ -118,27 +122,48 @@ def _run(cmd):
|
||||||
return (process.returncode, stdout, stderr)
|
return (process.returncode, stdout, stderr)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def _fail(module, cmd, out, err):
|
||||||
pip = None
|
msg = ''
|
||||||
virtualenv = None
|
if out:
|
||||||
env = None
|
msg += "stdout: %s" % (out, )
|
||||||
|
if err:
|
||||||
|
msg += "\n:stderr: %s" % (err, )
|
||||||
|
module.fail_json(cmd=cmd, msg=msg)
|
||||||
|
|
||||||
arg_spec = dict(
|
|
||||||
state=dict(default='present', choices=['absent', 'present', 'latest']),
|
def main():
|
||||||
name=dict(default=None, required=False),
|
state_map = dict(
|
||||||
version=dict(default=None, required=False),
|
present='install',
|
||||||
requirements=dict(default=None, required=False),
|
absent='uninstall -y',
|
||||||
virtualenv=dict(default=None, required=False),
|
latest='install -U',
|
||||||
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
|
state = module.params['state']
|
||||||
|
name = module.params['name']
|
||||||
|
version = module.params['version']
|
||||||
|
requirements = module.params['requirements']
|
||||||
|
use_mirrors = module.boolean(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')
|
||||||
|
if name and '=' in name:
|
||||||
|
module.fail_json(msg='version must be specified in the version parameter')
|
||||||
|
|
||||||
err = ''
|
err = ''
|
||||||
out = ''
|
out = ''
|
||||||
|
|
||||||
|
@ -146,100 +171,39 @@ def main():
|
||||||
|
|
||||||
if env:
|
if env:
|
||||||
virtualenv = module.get_bin_path('virtualenv', True)
|
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)
|
||||||
|
|
||||||
rc_venv, out_venv, err_venv = _ensure_virtualenv(module, env, virtualenv)
|
pip = _get_pip(module, env)
|
||||||
|
|
||||||
rc += rc_venv
|
cmd = '%s %s' % (pip, state_map[state])
|
||||||
out += out_venv
|
if state != 'absent' and use_mirrors:
|
||||||
err += err_venv
|
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
|
||||||
|
|
||||||
# On Debian and Ubuntu, pip is pip.
|
rc, out_pip, err_pip = _run(cmd)
|
||||||
# On Fedora18 and up, pip is python-pip.
|
out += out_pip
|
||||||
# On Fedora17 and below, CentOS and RedHat 6 and 5, pip is pip-python.
|
err += err_pip
|
||||||
# On Fedora, CentOS, and RedHat, the exception is in the virtualenv.
|
if rc == 1 and state == 'absent' and 'not installed' in out_pip:
|
||||||
# There, pip is just pip.
|
pass # rc is 1 when attempting to uninstall non-installed package
|
||||||
# Try pip with the virtualenv directory first.
|
elif rc != 0:
|
||||||
pip = module.get_bin_path('pip', False, ['%s/bin' % env])
|
_fail(module, cmd, out, err)
|
||||||
|
|
||||||
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']
|
|
||||||
name = module.params['name']
|
|
||||||
version = module.params['version']
|
|
||||||
requirements = module.params['requirements']
|
|
||||||
use_mirrors = module.boolean(module.params['use_mirrors'])
|
|
||||||
command_map = dict(present='install', absent='uninstall', latest='install')
|
|
||||||
|
|
||||||
if state == 'latest' and version is not None:
|
|
||||||
module.fail_json(msg='version is incompatible with state=latest')
|
|
||||||
|
|
||||||
if state == 'latest' and requirements is not None:
|
|
||||||
module.fail_json(msg='requirements is incompatible with state=latest')
|
|
||||||
|
|
||||||
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':
|
|
||||||
|
|
||||||
cmd = '%s %s %s --upgrade' % (pip, command_map[state], name)
|
|
||||||
rc_pip, out_pip, err_pip = _run(cmd)
|
|
||||||
|
|
||||||
rc += rc_pip
|
|
||||||
out += out_pip
|
|
||||||
err += err_pip
|
|
||||||
|
|
||||||
|
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…
Add table
Add a link
Reference in a new issue