diff --git a/library/pip b/library/pip index 87a9bd731eb..5d52ca5b8d1 100644 --- a/library/pip +++ b/library/pip @@ -62,23 +62,32 @@ options: 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=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)" - - code: "pip: requirements=/srv/webapps/my_app/src/requirements.txt" + - code: "pip: requirements=/my_app/requirements.txt" 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). + - 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 U(http://www.virtualenv.org/, virtualenv) 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 @@ -86,28 +95,23 @@ def _get_full_name(name, version=None): resp = name + '==' + version 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): - return 'Successfully installed' in out +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 _run(cmd): @@ -118,27 +122,48 @@ def _run(cmd): return (process.returncode, stdout, stderr) -def main(): - pip = None - virtualenv = None - env = None +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) - 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) + +def main(): + state_map = dict( + present='install', + absent='uninstall -y', + latest='install -U', ) module = AnsibleModule( - argument_spec=arg_spec, - required_one_of=[['name','requirements']], - mutually_exclusive=[['name','requirements']], + 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), + 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 = '' out = '' @@ -146,100 +171,39 @@ def main(): if env: 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 - out += out_venv - err += err_venv + 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 - # 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'] - 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 + rc, out_pip, err_pip = _run(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 - 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, state=state, requirements=requirements, virtualenv=env)