Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow arbitrary arguments to be passed to pip through pip.installed #52327

Merged
merged 15 commits into from
Apr 22, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions salt/modules/pip.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@
# Import python libs
import logging
import os

try:
import pkg_resources
except ImportError:
Expand Down Expand Up @@ -441,6 +442,7 @@ def install(pkgs=None, # pylint: disable=R0912,R0913,R0914
no_cache_dir=False,
cache_dir=None,
no_binary=None,
extra_args=None,
**kwargs):
'''
Install packages with pip
Expand Down Expand Up @@ -612,6 +614,24 @@ def install(pkgs=None, # pylint: disable=R0912,R0913,R0914
no_cache_dir
Disable the cache.

extra_args
pip keyword and positional arguments not yet implemented in salt

.. code-block:: yaml

salt '*' pip.install pandas extra_args="[{'--latest-pip-kwarg':'param'}, '--latest-pip-arg']"

.. warning::

If unsupported options are passed here that are not supported in a
minion's version of pip, a `No such option error` will be thrown.

Will be translated into the following pip command:

.. code-block:: bash

pip install pandas --latest-pip-kwarg param --latest-pip-arg

CLI Example:

.. code-block:: bash
Expand Down Expand Up @@ -895,6 +915,24 @@ def install(pkgs=None, # pylint: disable=R0912,R0913,R0914
if trusted_host:
cmd.extend(['--trusted-host', trusted_host])

if extra_args:
# These are arguments from the latest version of pip that
# have not yet been implemented in salt
for arg in extra_args:
# It is a keyword argument
if isinstance(arg, dict):
# There will only ever be one item in this dictionary
key, val = arg.popitem()
# Don't allow any recursion into keyword arg definitions
# Don't allow multiple definitions of a keyword
if isinstance(val, (dict, list)):
raise TypeError("Too many levels in: {}".format(key))
# This is a a normal one-to-one keyword argument
cmd.extend([key, val])
# It is a positional argument, append it to the list
else:
cmd.append(arg)

cmd_kwargs = dict(saltenv=saltenv, use_vt=use_vt, runas=user)

if kwargs:
Expand Down
23 changes: 22 additions & 1 deletion salt/states/pip_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@

# Import python libs
from __future__ import absolute_import, print_function, unicode_literals
import re

import logging
import re

try:
import pkg_resources
HAS_PKG_RESOURCES = True
Expand Down Expand Up @@ -341,6 +343,7 @@ def installed(name,
no_cache_dir=False,
cache_dir=None,
no_binary=None,
extra_args=None,
**kwargs):
'''
Make sure the package is installed
Expand Down Expand Up @@ -602,6 +605,23 @@ def installed(name,
- reload_modules: True
- exists_action: i

extra_args
pip keyword and positional arguments not yet implemented in salt

.. code-block:: yaml

pandas:
pip.installed:
- name: pandas
- extra_args:
- --latest-pip-kwarg: param
- --latest-pip-arg

.. warning::

If unsupported options are passed here that are not supported in a
minion's version of pip, a `No such option error` will be thrown.


.. _`virtualenv`: http://www.virtualenv.org/en/latest/
'''
Expand Down Expand Up @@ -838,6 +858,7 @@ def installed(name,
use_vt=use_vt,
trusted_host=trusted_host,
no_cache_dir=no_cache_dir,
extra_args=extra_args,
**kwargs
)

Expand Down
47 changes: 41 additions & 6 deletions tests/unit/modules/test_pip.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,18 @@

# Import python libs
from __future__ import absolute_import

import os
import sys

# Import Salt Testing libs
from tests.support.mixins import LoaderModuleMockMixin
from tests.support.unit import skipIf, TestCase
from tests.support.mock import NO_MOCK, NO_MOCK_REASON, MagicMock, patch

import salt.modules.pip as pip
# Import salt libs
import salt.utils.platform
import salt.modules.pip as pip
from salt.exceptions import CommandExecutionError
# Import Salt Testing libs
from tests.support.mixins import LoaderModuleMockMixin
from tests.support.mock import NO_MOCK, NO_MOCK_REASON, MagicMock, patch
from tests.support.unit import skipIf, TestCase


@skipIf(NO_MOCK, NO_MOCK_REASON)
Expand Down Expand Up @@ -803,6 +803,41 @@ def test_install_multiple_requirements_arguments_in_resulting_command(self):
python_shell=False,
)

def test_install_extra_args_arguments_in_resulting_command(self):
pkg = 'pep8'
mock = MagicMock(return_value={'retcode': 0, 'stdout': ''})
with patch.dict(pip.__salt__, {'cmd.run_all': mock}):
pip.install(pkg, extra_args=[
{"--latest-pip-kwarg": "param"},
"--latest-pip-arg"
])
expected = [
sys.executable, '-m', 'pip', 'install', pkg,
"--latest-pip-kwarg", "param", "--latest-pip-arg"
]
mock.assert_called_with(
expected,
saltenv='base',
runas=None,
use_vt=False,
python_shell=False,
)

def test_install_extra_args_arguments_recursion_error(self):
pkg = 'pep8'
mock = MagicMock(return_value={'retcode': 0, 'stdout': ''})
with patch.dict(pip.__salt__, {'cmd.run_all': mock}):

self.assertRaises(TypeError, lambda: pip.install(
pkg, extra_args=[
{"--latest-pip-kwarg": ["param1", "param2"]},
]))

self.assertRaises(TypeError, lambda: pip.install(
pkg, extra_args=[
{"--latest-pip-kwarg": [{"--too-deep": dict()}]},
]))

def test_uninstall_multiple_requirements_arguments_in_resulting_command(self):
with patch('salt.modules.pip._get_cached_requirements') as get_cached_requirements:
cached_reqs = [
Expand Down