Merge pull request #33522 from jfindlay/mac_pkg

rework modules.mac_brew.latest_version to work around brew version inconsistency
This commit is contained in:
Mike Place 2016-05-26 07:19:25 -07:00
commit 0bc881b4da
3 changed files with 188 additions and 65 deletions

View file

@ -18,6 +18,9 @@ import logging
import salt.utils
from salt.exceptions import CommandExecutionError, MinionError
# Import third party libs
import json
log = logging.getLogger(__name__)
# Define the module's virtual name
@ -51,7 +54,9 @@ def _tap(tap, runas=None):
return True
cmd = 'brew tap {0}'.format(tap)
if _call_brew(cmd)['retcode']:
try:
_call_brew(cmd)
except CommandExecutionError:
log.error('Failed to tap "{0}"'.format(tap))
return False
@ -73,11 +78,18 @@ def _call_brew(cmd, redirect_stderr=False):
'''
user = __salt__['file.get_user'](_homebrew_bin())
runas = user if user != __opts__['user'] else None
return __salt__['cmd.run_all'](cmd,
runas=runas,
output_loglevel='trace',
python_shell=False,
redirect_stderr=redirect_stderr)
ret = __salt__['cmd.run_all'](cmd,
runas=runas,
output_loglevel='trace',
python_shell=False,
redirect_stderr=redirect_stderr)
if ret['retcode'] != 0:
raise CommandExecutionError(
'stdout: {stdout}\n'
'stderr: {stderr}\n'
'retcode: {retcode}\n'.format(**ret)
)
return ret
def list_pkgs(versions_as_list=False, **kwargs):
@ -147,9 +159,6 @@ def latest_version(*names, **kwargs):
Return the latest version of the named package available for upgrade or
installation
Note that this currently not fully implemented but needs to return
something to avoid a traceback when calling pkg.latest.
CLI Example:
.. code-block:: bash
@ -158,28 +167,36 @@ def latest_version(*names, **kwargs):
salt '*' pkg.latest_version <package1> <package2> <package3>
'''
refresh = salt.utils.is_true(kwargs.pop('refresh', True))
if refresh:
refresh_db()
if len(names) <= 0:
return ''
else:
ret = {}
pkg_upgrades = 'brew outdated --verbose'
pkg_installed = list_pkgs()
call = _call_brew(pkg_upgrades)
# Get dictionaries of installed and upgradeable packages
installed = list_pkgs()
upgrade = list_upgrades()
for name in names:
ret[name] = ''
if name in pkg_installed:
if name in call['stdout']:
updates = call['stdout'].split()[-1].strip(")")
ret[name] = updates
if len(names) == 0:
return ''
ret = {}
for name in names:
if name in installed:
if name in upgrade:
ret[name] = upgrade[name]
else:
new_version = __salt__['cmd.run']('brew info {0}'.format(name))
to_install = new_version.split("\n")[0].split()[2]
ret[name] = ''.join(to_install)
ret[name] = ''
else:
pkg_info = _info(name)
# brew does not include the revision in the version string for
# uninstalled packages, but it does for installed packages, so
# append it here so that the pkg.uptodate function works
version = pkg_info[name]['versions']['stable']
revision = pkg_info[name]['revision']
if revision > 0:
version += '_{0}'.format(revision)
ret[name] = version
# Return a string if only one package name passed
if len(names) == 1:
return ret[names[0]]
return ret
@ -387,20 +404,20 @@ def list_upgrades(refresh=True):
if refresh:
refresh_db()
cmd = 'brew outdated'
call = _call_brew(cmd)
if call['retcode'] != 0:
comment = ''
if 'stderr' in call:
comment += call['stderr']
if 'stdout' in call:
comment += call['stdout']
raise CommandExecutionError(
'{0}'.format(comment)
)
else:
out = call['stdout']
return out.splitlines()
res = _call_brew(['brew', 'outdated', '--json=v1'])
ret = {}
try:
data = json.loads(res['stdout'])
except ValueError as err:
msg = 'unable to interpret output from "brew outdated": {0}'.format(err)
log.error(msg)
raise CommandExecutionError(msg)
for pkg in data:
# current means latest available to brew
ret[pkg['name']] = pkg['current_version']
return ret
def upgrade_available(pkg):
@ -457,3 +474,49 @@ def upgrade(refresh=True):
ret['changes'] = salt.utils.compare_dicts(old, new)
return ret
def _info(*names):
'''
Return the information of the named package(s)
.. versionadded:: 2016.3.1
names
The names of the packages for which to return information.
'''
cmd = ['brew', 'info', '--json=v1']
cmd.extend(names)
res = _call_brew(cmd)
ret = {}
try:
data = json.loads(res['stdout'])
except ValueError as err:
msg = 'unable to interpret output from "brew info": {0}'.format(err)
log.error(msg)
raise CommandExecutionError(msg)
for pkg in data:
ret[pkg['name']] = pkg
return ret
def info_installed(*names):
'''
Return the information of the named package(s) installed on the system.
.. versionadded:: 2016.3.1
names
The names of the packages for which to return information.
CLI example:
.. code-block:: bash
salt '*' pkg.info_installed <package1>
salt '*' pkg.info_installed <package1> <package2> <package3> ...
'''
return _info(*names)

View file

@ -12,7 +12,6 @@ from salttesting import skipIf
from salttesting.helpers import (
destructiveTest,
ensure_in_syspath,
requires_system_grains
)
ensure_in_syspath('../../')
@ -21,12 +20,17 @@ import integration
import salt.utils
from salt.exceptions import CommandExecutionError
# Import third party libs
import salt.ext.six as six
# Brew doesn't support local package installation - So, let's
# Grab some small packages available online for brew
ADD_PKG = 'algol68g'
DEL_PKG = 'acme'
@destructiveTest
@skipIf(os.geteuid() != 0, 'You must be logged in as root to run this test')
class BrewModuleTest(integration.ModuleCase):
'''
Integration tests for the brew module
@ -51,10 +55,7 @@ class BrewModuleTest(integration.ModuleCase):
'You must have brew installed to run these tests'
)
@destructiveTest
@skipIf(os.geteuid() != 0, 'You must be logged in as root to run this test')
@requires_system_grains
def test_brew_install(self, grains=None):
def test_brew_install(self):
'''
Tests the installation of packages
'''
@ -70,10 +71,7 @@ class BrewModuleTest(integration.ModuleCase):
self.run_function('pkg.remove', [ADD_PKG])
raise
@destructiveTest
@skipIf(os.geteuid() != 0, 'You must be logged in as root to run this test')
@requires_system_grains
def test_remove(self, grains=None):
def test_remove(self):
'''
Tests the removal of packages
'''
@ -96,10 +94,7 @@ class BrewModuleTest(integration.ModuleCase):
self.run_function('pkg.remove', [DEL_PKG])
raise
@destructiveTest
@skipIf(os.geteuid() != 0, 'You must be logged in as root to run this test')
@requires_system_grains
def test_mac_brew_pkg_version(self, grains=None):
def test_version(self):
'''
Test pkg.version for mac. Installs
a package and then checks we can get
@ -129,20 +124,79 @@ class BrewModuleTest(integration.ModuleCase):
self.run_function('pkg.remove', [ADD_PKG])
raise
@destructiveTest
@skipIf(os.geteuid() != 0, 'You must be logged in as root to run this test')
@requires_system_grains
def test_mac_brew_refresh_db(self, grains=None):
def test_latest_version(self):
'''
Test pkg.latest_version:
- get the latest version available
- install the package
- get the latest version available
- check that the latest version is empty after installing it
'''
try:
self.run_function('pkg.remove', [ADD_PKG])
uninstalled_latest = self.run_function('pkg.latest_version', [ADD_PKG])
self.run_function('pkg.install', [ADD_PKG])
installed_latest = self.run_function('pkg.latest_version', [ADD_PKG])
version = self.run_function('pkg.version', [ADD_PKG])
try:
self.assertTrue(isinstance(uninstalled_latest, six.string_types))
self.assertEqual(installed_latest, '')
except AssertionError:
self.run_function('pkg.remove', [ADD_PKG])
raise
except CommandExecutionError:
self.run_function('pkg.remove', [ADD_PKG])
raise
def test_refresh_db(self):
'''
Integration test to ensure pkg.refresh_db works with brew
'''
refresh_brew = self.run_function('pkg.refresh_db')
self.assertTrue(refresh_brew)
@destructiveTest
@skipIf(os.geteuid() != 0, 'You must be logged in as root to run this test')
@requires_system_grains
def tearDown(self, grains=None):
def test_list_upgrades(self):
'''
Test pkg.list_upgrades: data is in the form {'name1': 'version1',
'name2': 'version2', ... }
'''
try:
upgrades = self.run_function('pkg.list_upgrades')
try:
self.assertTrue(isinstance(upgrades, dict))
if len(upgrades):
for name in upgrades:
self.assertTrue(isinstance(name, six.string_types))
self.assertTrue(isinstance(upgrades[name], six.string_types))
except AssertionError:
self.run_function('pkg.remove', [ADD_PKG])
raise
except CommandExecutionError:
self.run_function('pkg.remove', [ADD_PKG])
raise
def test_info_installed(self):
'''
Test pkg.info_installed: info returned has certain fields used by
mac_brew.latest_version
'''
try:
self.run_function('pkg.install', [ADD_PKG])
info = self.run_function('pkg.info_installed', [ADD_PKG])
try:
self.assertTrue(ADD_PKG in info)
self.assertTrue('versions' in info[ADD_PKG])
self.assertTrue('revision' in info[ADD_PKG])
self.assertTrue('stable' in info[ADD_PKG]['versions'])
except AssertionError:
self.run_function('pkg.remove', [ADD_PKG])
raise
except CommandExecutionError:
self.run_function('pkg.remove', [ADD_PKG])
raise
def tearDown(self):
'''
Clean up after tests
'''

View file

@ -8,6 +8,7 @@ from __future__ import absolute_import
# Import Salt Libs
from salt.modules import mac_brew
from salt.exceptions import CommandExecutionError
# Import Salt Testing Libs
from salttesting import skipIf, TestCase
@ -38,7 +39,8 @@ class BrewTestCase(TestCase):
'''
Tests the return of the list of taps
'''
mock_taps = MagicMock(return_value={'stdout': TAPS_STRING})
mock_taps = MagicMock(return_value={'stdout': TAPS_STRING,
'retcode': 0})
mock_user = MagicMock(return_value='foo')
mock_cmd = MagicMock(return_value='')
with patch.dict(mac_brew.__salt__, {'file.get_user': mock_user,
@ -60,7 +62,9 @@ class BrewTestCase(TestCase):
'''
Tests if the tap installation failed
'''
mock_failure = MagicMock(return_value={'retcode': 1})
mock_failure = MagicMock(return_value={'stdout': '',
'stderr': '',
'retcode': 1})
mock_user = MagicMock(return_value='foo')
mock_cmd = MagicMock(return_value='')
with patch.dict(mac_brew.__salt__, {'cmd.run_all': mock_failure,
@ -147,10 +151,12 @@ class BrewTestCase(TestCase):
Tests an update of homebrew package repository failure
'''
mock_user = MagicMock(return_value='foo')
mock_failure = MagicMock(return_value={'retcode': 1})
mock_failure = MagicMock(return_value={'stdout': '',
'stderr': '',
'retcode': 1})
with patch.dict(mac_brew.__salt__, {'file.get_user': mock_user,
'cmd.run_all': mock_failure}):
self.assertFalse(mac_brew.refresh_db())
self.assertRaises(CommandExecutionError, mac_brew.refresh_db)
@patch('salt.modules.mac_brew._homebrew_bin',
MagicMock(return_value=HOMEBREW_BIN))