diff --git a/packaging/language/pip.py b/packaging/language/pip.py index 484c8bfc0d8..d9b403215bf 100755 --- a/packaging/language/pip.py +++ b/packaging/language/pip.py @@ -22,6 +22,7 @@ import tempfile import re import os +import sys DOCUMENTATION = ''' --- @@ -114,6 +115,17 @@ options: version_added: "1.3" required: false default: null + umask: + description: + - The system umask to apply before installing the pip package. This is + useful, for example, when installing on systems that have a very + restrictive umask by default (e.g., 0077) and you want to pip install + packages which are to be used by all users. Note that this requires you + to specify desired umask mode in octal, with a leading 0 (e.g., 0077). + version_added: "2.1" + required: false + default: null + notes: - Please note that virtualenv (U(http://www.virtualenv.org/)) must be installed on the remote host if the virtualenv parameter is specified and the virtualenv needs to be initialized. requirements: [ "virtualenv", "pip" ] @@ -159,6 +171,10 @@ EXAMPLES = ''' # Install (Bottle), forcing reinstallation if it's already installed - pip: name=bottle state=forcereinstall + +# Install (Bottle) while ensuring the umask is 0022 (to ensure other users can use it) +- pip: name=bottle umask=0022 + become: True ''' def _get_cmd_options(module, cmd): @@ -258,6 +274,7 @@ def main(): editable=dict(default='yes', type='bool', required=False), chdir=dict(default=None, required=False, type='path'), executable=dict(default=None, required=False), + umask=dict(reqiured=False,default=None), ), required_one_of=[['name', 'requirements']], mutually_exclusive=[['name', 'requirements']], @@ -271,129 +288,148 @@ def main(): extra_args = module.params['extra_args'] virtualenv_python = module.params['virtualenv_python'] chdir = module.params['chdir'] + umask = module.params['umask'] + + if umask and not isinstance(umask, int): + try: + umask = int(umask, 8) + except Exception: + module.fail_json(msg="umask must be an octal integer", + details=str(sys.exc_info()[1])) + + + old_umask = None + if umask != None: + old_umask = os.umask(umask) + try: + if state == 'latest' and version is not None: + module.fail_json(msg='version is incompatible with state=latest') + + if chdir is None: + # this is done to avoid permissions issues with privilege escalation and virtualenvs + chdir = tempfile.gettempdir() + + err = '' + out = '' + + env = module.params['virtualenv'] + virtualenv_command = module.params['virtualenv_command'] + + if env: + env = os.path.expanduser(env) + if not os.path.exists(os.path.join(env, 'bin', 'activate')): + if module.check_mode: + module.exit_json(changed=True) + + cmd = os.path.expanduser(virtualenv_command) + if os.path.basename(cmd) == cmd: + cmd = module.get_bin_path(virtualenv_command, True) + + if module.params['virtualenv_site_packages']: + cmd += ' --system-site-packages' + else: + cmd_opts = _get_cmd_options(module, cmd) + if '--no-site-packages' in cmd_opts: + cmd += ' --no-site-packages' + + if virtualenv_python: + cmd += ' -p%s' % virtualenv_python + + cmd = "%s %s" % (cmd, env) + rc, out_venv, err_venv = module.run_command(cmd, cwd=chdir) + out += out_venv + err += err_venv + if rc != 0: + _fail(module, cmd, out, err) + + pip = _get_pip(module, env, module.params['executable']) + + cmd = '%s %s' % (pip, state_map[state]) + + # If there's a virtualenv we want things we install to be able to use other + # installations that exist as binaries within this virtualenv. Example: we + # install cython and then gevent -- gevent needs to use the cython binary, + # not just a python package that will be found by calling the right python. + # So if there's a virtualenv, we add that bin/ to the beginning of the PATH + # in run_command by setting path_prefix here. + path_prefix = None + if env: + path_prefix = "/".join(pip.split('/')[:-1]) + + # Automatically apply -e option to extra_args when source is a VCS url. VCS + # includes those beginning with svn+, git+, hg+ or bzr+ + has_vcs = bool(name and re.match(r'(svn|git|hg|bzr)\+', name)) + if has_vcs and module.params['editable']: + 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 state == 'latest' and version is not None: - module.fail_json(msg='version is incompatible with state=latest') - - if chdir is None: - # this is done to avoid permissions issues with privilege escalation and virtualenvs - chdir = tempfile.gettempdir() - - err = '' - out = '' + if extra_args: + cmd += ' %s' % extra_args + if name: + cmd += ' %s' % _get_full_name(name, version) + elif requirements: + cmd += ' -r %s' % requirements - env = module.params['virtualenv'] - virtualenv_command = module.params['virtualenv_command'] - if env: - env = os.path.expanduser(env) - if not os.path.exists(os.path.join(env, 'bin', 'activate')): - if module.check_mode: + if module.check_mode: + if extra_args or requirements or state == 'latest' or not name: + module.exit_json(changed=True) + elif has_vcs: module.exit_json(changed=True) - cmd = os.path.expanduser(virtualenv_command) - if os.path.basename(cmd) == cmd: - cmd = module.get_bin_path(virtualenv_command, True) - - if module.params['virtualenv_site_packages']: - cmd += ' --system-site-packages' - else: - cmd_opts = _get_cmd_options(module, cmd) - if '--no-site-packages' in cmd_opts: - cmd += ' --no-site-packages' + freeze_cmd = '%s freeze' % pip - if virtualenv_python: - cmd += ' -p%s' % virtualenv_python + rc, out_pip, err_pip = module.run_command(freeze_cmd, cwd=chdir) - cmd = "%s %s" % (cmd, env) - rc, out_venv, err_venv = module.run_command(cmd, cwd=chdir) - out += out_venv - err += err_venv if rc != 0: - _fail(module, cmd, out, err) - - pip = _get_pip(module, env, module.params['executable']) - - cmd = '%s %s' % (pip, state_map[state]) - - # If there's a virtualenv we want things we install to be able to use other - # installations that exist as binaries within this virtualenv. Example: we - # install cython and then gevent -- gevent needs to use the cython binary, - # not just a python package that will be found by calling the right python. - # So if there's a virtualenv, we add that bin/ to the beginning of the PATH - # in run_command by setting path_prefix here. - path_prefix = None - if env: - path_prefix = "/".join(pip.split('/')[:-1]) - - # Automatically apply -e option to extra_args when source is a VCS url. VCS - # includes those beginning with svn+, git+, hg+ or bzr+ - has_vcs = bool(name and re.match(r'(svn|git|hg|bzr)\+', name)) - if has_vcs and module.params['editable']: - 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 extra_args: - cmd += ' %s' % extra_args - if name: - cmd += ' %s' % _get_full_name(name, version) - elif requirements: - cmd += ' -r %s' % requirements - + module.exit_json(changed=True) - if module.check_mode: - if extra_args or requirements or state == 'latest' or not name: - module.exit_json(changed=True) - elif has_vcs: - module.exit_json(changed=True) + out += out_pip + err += err_pip - freeze_cmd = '%s freeze' % pip + is_present = _is_present(name, version, out.split()) - rc, out_pip, err_pip = module.run_command(freeze_cmd, cwd=chdir) + changed = (state == 'present' and not is_present) or (state == 'absent' and is_present) + module.exit_json(changed=changed, cmd=freeze_cmd, stdout=out, stderr=err) - if rc != 0: - module.exit_json(changed=True) + if requirements or has_vcs: + freeze_cmd = '%s freeze' % pip + out_freeze_before = module.run_command(freeze_cmd, cwd=chdir)[1] + else: + out_freeze_before = None + rc, out_pip, err_pip = module.run_command(cmd, path_prefix=path_prefix, cwd=chdir) out += out_pip err += err_pip - - is_present = _is_present(name, version, out.split()) - - changed = (state == 'present' and not is_present) or (state == 'absent' and is_present) or (state == 'forcereinstall' and is_present) - module.exit_json(changed=changed, cmd=freeze_cmd, stdout=out, stderr=err) - - if requirements or has_vcs: - freeze_cmd = '%s freeze' % pip - out_freeze_before = module.run_command(freeze_cmd, cwd=chdir)[1] - else: - out_freeze_before = None - - rc, out_pip, err_pip = module.run_command(cmd, path_prefix=path_prefix, cwd=chdir) - out += out_pip - err += err_pip - if rc == 1 and state == 'absent' and \ - ('not installed' in out_pip or 'not installed' in err_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: - if out_freeze_before is None: - changed = 'Successfully installed' in out_pip + if rc == 1 and state == 'absent' and \ + ('not installed' in out_pip or 'not installed' in err_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: - out_freeze_after = module.run_command(freeze_cmd, cwd=chdir)[1] - changed = out_freeze_before != out_freeze_after - - module.exit_json(changed=changed, cmd=cmd, name=name, version=version, - state=state, requirements=requirements, virtualenv=env, - stdout=out, stderr=err) + if out_freeze_before is None: + changed = 'Successfully installed' in out_pip + else: + if out_freeze_before is None: + changed = 'Successfully installed' in out_pip + else: + out_freeze_after = module.run_command(freeze_cmd, cwd=chdir)[1] + changed = out_freeze_before != out_freeze_after + + module.exit_json(changed=changed, cmd=cmd, name=name, version=version, + state=state, requirements=requirements, virtualenv=env, + stdout=out, stderr=err) + finally: + if old_umask != None: + os.umask(old_umask) # import module snippets from ansible.module_utils.basic import *