#!/usr/bin/python # -*- coding: utf-8 -*- # (c) 2012, Matt Wright # # 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 . # DOCUMENTATION = ''' --- module: pip short_description: Manages Python library dependencies. description: - Manage Python library dependencies. version_added: "0.7" options: name: description: - The name of a Python library to install required: true 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 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" ] 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" description: "Install I(Flask) (U(http://flask.pocoo.org/)) into the specified I(virtualenv)" - code: "pip: requirements=/srv/webapps/my_app/src/requirements.txt" description: Install specified python requirements. - code: "pip: requirements=/srv/webapps/my_app/src/requirements.txt virtualenv=/srv/webapps/my_app/venv" description: Install specified python requirements in indicated I(virtualenv). 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 else: 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 _run(cmd): # returns (rc, stdout, stderr) from shell command process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True) stdout, stderr = process.communicate() return (process.returncode, stdout, stderr) def main(): pip = None virtualenv = None env = None 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( argument_spec=arg_spec, 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'] 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 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) # this is magic, see lib/ansible/module_common.py #<> main()