mirror of
https://github.com/saltstack/salt.git
synced 2025-04-17 10:10:20 +00:00
Merge pull request #51004 from twangboy/win_wusa
Add tests for the win_wusa state and module
This commit is contained in:
commit
cecd108aff
4 changed files with 614 additions and 89 deletions
|
@ -6,20 +6,22 @@ Microsoft Update files management via wusa.exe
|
|||
:platform: Windows
|
||||
:depends: PowerShell
|
||||
|
||||
.. versionadded:: Neon
|
||||
.. versionadded:: 2018.3.4
|
||||
'''
|
||||
|
||||
# Import python libs
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
import logging
|
||||
import os
|
||||
|
||||
# Import salt libs
|
||||
import salt.utils.platform
|
||||
from salt.exceptions import CommandExecutionError
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
# Define the module's virtual name
|
||||
__virtualname__ = 'win_wusa'
|
||||
__virtualname__ = 'wusa'
|
||||
|
||||
|
||||
def __virtual__():
|
||||
|
@ -36,58 +38,189 @@ def __virtual__():
|
|||
return __virtualname__
|
||||
|
||||
|
||||
def is_installed(kb):
|
||||
def _pshell_json(cmd, cwd=None):
|
||||
'''
|
||||
Execute the desired powershell command and ensure that it returns data
|
||||
in JSON format and load that into python
|
||||
'''
|
||||
if 'convertto-json' not in cmd.lower():
|
||||
cmd = '{0} | ConvertTo-Json'.format(cmd)
|
||||
log.debug('PowerShell: %s', cmd)
|
||||
ret = __salt__['cmd.run_all'](cmd, shell='powershell', cwd=cwd)
|
||||
|
||||
if 'pid' in ret:
|
||||
del ret['pid']
|
||||
|
||||
if ret.get('stderr', ''):
|
||||
error = ret['stderr'].splitlines()[0]
|
||||
raise CommandExecutionError(error, info=ret)
|
||||
|
||||
if 'retcode' not in ret or ret['retcode'] != 0:
|
||||
# run_all logs an error to log.error, fail hard back to the user
|
||||
raise CommandExecutionError(
|
||||
'Issue executing PowerShell {0}'.format(cmd), info=ret)
|
||||
|
||||
# Sometimes Powershell returns an empty string, which isn't valid JSON
|
||||
if ret['stdout'] == '':
|
||||
ret['stdout'] = '{}'
|
||||
|
||||
try:
|
||||
ret = salt.utils.json.loads(ret['stdout'], strict=False)
|
||||
except ValueError:
|
||||
raise CommandExecutionError(
|
||||
'No JSON results from PowerShell', info=ret)
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def is_installed(name):
|
||||
'''
|
||||
Check if a specific KB is installed.
|
||||
|
||||
Args:
|
||||
|
||||
name (str):
|
||||
The name of the KB to check
|
||||
|
||||
Returns:
|
||||
bool: ``True`` if installed, otherwise ``False``
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' win_wusa.is_installed KB123456
|
||||
salt '*' wusa.is_installed KB123456
|
||||
'''
|
||||
get_hotfix_result = __salt__['cmd.powershell_all']('Get-HotFix -Id {0}'.format(kb), ignore_retcode=True)
|
||||
|
||||
return get_hotfix_result['retcode'] == 0
|
||||
return __salt__['cmd.retcode'](cmd='Get-HotFix -Id {0}'.format(name),
|
||||
shell='powershell',
|
||||
ignore_retcode=True) == 0
|
||||
|
||||
|
||||
def install(path):
|
||||
def install(path, restart=False):
|
||||
'''
|
||||
Install a KB from a .msu file.
|
||||
Some KBs will need a reboot, but this function does not manage it.
|
||||
You may have to manage reboot yourself after installation.
|
||||
|
||||
Args:
|
||||
|
||||
path (str):
|
||||
The full path to the msu file to install
|
||||
|
||||
restart (bool):
|
||||
``True`` to force a restart if required by the installation. Adds
|
||||
the ``/forcerestart`` switch to the ``wusa.exe`` command. ``False``
|
||||
will add the ``/norestart`` switch instead. Default is ``False``
|
||||
|
||||
Returns:
|
||||
bool: ``True`` if successful, otherwise ``False``
|
||||
|
||||
Raise:
|
||||
CommandExecutionError: If the package is already installed or an error
|
||||
is encountered
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' win_wusa.install C:/temp/KB123456.msu
|
||||
salt '*' wusa.install C:/temp/KB123456.msu
|
||||
'''
|
||||
return __salt__['cmd.run_all']('wusa.exe {0} /quiet /norestart'.format(path), ignore_retcode=True)
|
||||
# Build the command
|
||||
cmd = ['wusa.exe', path, '/quiet']
|
||||
if restart:
|
||||
cmd.append('/forcerestart')
|
||||
else:
|
||||
cmd.append('/norestart')
|
||||
|
||||
# Run the command
|
||||
ret_code = __salt__['cmd.retcode'](cmd, ignore_retcode=True)
|
||||
|
||||
# Check the ret_code
|
||||
file_name = os.path.basename(path)
|
||||
errors = {2359302: '{0} is already installed'.format(file_name),
|
||||
87: 'Unknown error'}
|
||||
if ret_code in errors:
|
||||
raise CommandExecutionError(errors[ret_code])
|
||||
elif ret_code:
|
||||
raise CommandExecutionError('Unknown error: {0}'.format(ret_code))
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def uninstall(kb):
|
||||
def uninstall(path, restart=False):
|
||||
'''
|
||||
Uninstall a specific KB.
|
||||
|
||||
CLI Example:
|
||||
Args:
|
||||
|
||||
.. code-block:: bash
|
||||
path (str):
|
||||
The full path to the msu file to uninstall. This can also be just
|
||||
the name of the KB to uninstall
|
||||
|
||||
salt '*' win_wusa.uninstall KB123456
|
||||
'''
|
||||
return __salt__['cmd.run_all']('wusa.exe /uninstall /kb:{0} /quiet /norestart'.format(kb[2:]), ignore_retcode=True)
|
||||
restart (bool):
|
||||
``True`` to force a restart if required by the installation. Adds
|
||||
the ``/forcerestart`` switch to the ``wusa.exe`` command. ``False``
|
||||
will add the ``/norestart`` switch instead. Default is ``False``
|
||||
|
||||
Returns:
|
||||
bool: ``True`` if successful, otherwise ``False``
|
||||
|
||||
def list_kbs():
|
||||
'''
|
||||
Return a list of dictionaries, one dictionary for each installed KB.
|
||||
The HotFixID key contains the ID of the KB.
|
||||
Raises:
|
||||
CommandExecutionError: If an error is encountered
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' win_wusa.list_kbs
|
||||
salt '*' wusa.uninstall KB123456
|
||||
|
||||
# or
|
||||
|
||||
salt '*' wusa.uninstall C:/temp/KB123456.msu
|
||||
'''
|
||||
return __salt__['cmd.powershell']('Get-HotFix')
|
||||
# Build the command
|
||||
cmd = ['wusa.exe', '/uninstall', '/quiet']
|
||||
kb = os.path.splitext(os.path.basename(path))[0]
|
||||
if os.path.exists(path):
|
||||
cmd.append(path)
|
||||
else:
|
||||
cmd.append(
|
||||
'/kb:{0}'.format(kb[2:] if kb.lower().startswith('kb') else kb))
|
||||
if restart:
|
||||
cmd.append('/forcerestart')
|
||||
else:
|
||||
cmd.append('/norestart')
|
||||
|
||||
# Run the command
|
||||
ret_code = __salt__['cmd.retcode'](cmd, ignore_retcode=True)
|
||||
|
||||
# Check the ret_code
|
||||
# If you pass /quiet and specify /kb, you'll always get retcode 87 if there
|
||||
# is an error. Use the actual file to get a more descriptive error
|
||||
errors = {-2145116156: '{0} does not support uninstall'.format(kb),
|
||||
2359303: '{0} not installed'.format(kb),
|
||||
87: 'Unknown error. Try specifying an .msu file'}
|
||||
if ret_code in errors:
|
||||
raise CommandExecutionError(errors[ret_code])
|
||||
elif ret_code:
|
||||
raise CommandExecutionError('Unknown error: {0}'.format(ret_code))
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def list():
|
||||
'''
|
||||
Get a list of updates installed on the machine
|
||||
|
||||
Returns:
|
||||
list: A list of installed updates
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' wusa.list
|
||||
'''
|
||||
kbs = []
|
||||
ret = _pshell_json('Get-HotFix | Select HotFixID')
|
||||
for item in ret:
|
||||
kbs.append(item['HotFixID'])
|
||||
return kbs
|
||||
|
|
|
@ -2,10 +2,10 @@
|
|||
'''
|
||||
Microsoft Updates (KB) Management
|
||||
|
||||
This module provides the ability to enforce KB installations
|
||||
from files (.msu), without WSUS.
|
||||
This module provides the ability to enforce KB installations from files (.msu),
|
||||
without WSUS or Windows Update
|
||||
|
||||
.. versionadded:: Neon
|
||||
.. versionadded:: 2018.3.4
|
||||
'''
|
||||
|
||||
# Import python libs
|
||||
|
@ -14,13 +14,14 @@ import logging
|
|||
|
||||
# Import salt libs
|
||||
import salt.utils.platform
|
||||
import salt.exceptions
|
||||
import salt.utils.url
|
||||
from salt.exceptions import SaltInvocationError
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# Define the module's virtual name
|
||||
__virtualname__ = 'win_wusa'
|
||||
__virtualname__ = 'wusa'
|
||||
|
||||
|
||||
def __virtual__():
|
||||
|
@ -35,81 +36,113 @@ def __virtual__():
|
|||
|
||||
def installed(name, source):
|
||||
'''
|
||||
Enforce the installed state of a KB
|
||||
Ensure an update is installed on the minion
|
||||
|
||||
name
|
||||
Name of the Windows KB ("KB123456")
|
||||
source
|
||||
Source of .msu file corresponding to the KB
|
||||
Args:
|
||||
|
||||
name(str):
|
||||
Name of the Windows KB ("KB123456")
|
||||
|
||||
source (str):
|
||||
Source of .msu file corresponding to the KB
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
KB123456:
|
||||
wusa.installed:
|
||||
- source: salt://kb123456.msu
|
||||
'''
|
||||
ret = {
|
||||
'name': name,
|
||||
'changes': {},
|
||||
'result': False,
|
||||
'comment': '',
|
||||
}
|
||||
ret = {'name': name,
|
||||
'changes': {},
|
||||
'result': False,
|
||||
'comment': ''}
|
||||
|
||||
# Start with basic error-checking. Do all the passed parameters make sense
|
||||
# and agree with each-other?
|
||||
if not name or not source:
|
||||
raise salt.exceptions.SaltInvocationError(
|
||||
'Arguments "name" and "source" are mandatory.')
|
||||
# Input validation
|
||||
if not name:
|
||||
raise SaltInvocationError('Must specify a KB "name"')
|
||||
if not source:
|
||||
raise SaltInvocationError('Must specify a "source" file to install')
|
||||
|
||||
# Check the current state of the system. Does anything need to change?
|
||||
current_state = __salt__['win_wusa.is_installed'](name)
|
||||
|
||||
if current_state:
|
||||
# Is the KB already installed
|
||||
if __salt__['wusa.is_installed'](name):
|
||||
ret['result'] = True
|
||||
ret['comment'] = 'KB already installed'
|
||||
ret['comment'] = '{0} already installed'.format(name)
|
||||
return ret
|
||||
|
||||
# The state of the system does need to be changed. Check if we're running
|
||||
# in ``test=true`` mode.
|
||||
# Check for test=True
|
||||
if __opts__['test'] is True:
|
||||
ret['comment'] = 'The KB "{0}" will be installed.'.format(name)
|
||||
ret['changes'] = {
|
||||
'old': current_state,
|
||||
'new': True,
|
||||
}
|
||||
|
||||
# Return ``None`` when running with ``test=true``.
|
||||
ret['result'] = None
|
||||
|
||||
ret['comment'] = '{0} would be installed'.format(name)
|
||||
ret['result'] = None
|
||||
return ret
|
||||
|
||||
try:
|
||||
result = __states__['file.cached'](source,
|
||||
skip_verify=True,
|
||||
saltenv=__env__)
|
||||
except Exception as exc:
|
||||
msg = 'Failed to cache {0}: {1}'.format(
|
||||
salt.utils.url.redact_http_basic_auth(source),
|
||||
exc.__str__())
|
||||
log.exception(msg)
|
||||
# Cache the file
|
||||
cached_source_path = __salt__['cp.cache_file'](path=source, saltenv=__env__)
|
||||
if not cached_source_path:
|
||||
msg = 'Unable to cache {0} from saltenv "{1}"'.format(
|
||||
salt.utils.url.redact_http_basic_auth(source), __env__)
|
||||
ret['comment'] = msg
|
||||
return ret
|
||||
|
||||
if result['result']:
|
||||
# Get the path of the file in the minion cache
|
||||
cached = __salt__['cp.is_cached'](source, saltenv=__env__)
|
||||
# Install the KB
|
||||
__salt__['wusa.install'](cached_source_path)
|
||||
|
||||
# Verify successful install
|
||||
if __salt__['wusa.is_installed'](name):
|
||||
ret['comment'] = '{0} was installed'.format(name)
|
||||
ret['changes'] = {'old': False, 'new': True}
|
||||
ret['result'] = True
|
||||
else:
|
||||
log.debug(
|
||||
'failed to download %s',
|
||||
salt.utils.url.redact_http_basic_auth(source)
|
||||
)
|
||||
return result
|
||||
|
||||
# Finally, make the actual change and return the result.
|
||||
new_state = __salt__['win_wusa.install'](cached)
|
||||
|
||||
ret['comment'] = 'The KB "{0}" was installed!'.format(name)
|
||||
|
||||
ret['changes'] = {
|
||||
'old': current_state,
|
||||
'new': new_state,
|
||||
}
|
||||
|
||||
ret['result'] = True
|
||||
ret['comment'] = '{0} failed to install'.format(name)
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def uninstalled(name):
|
||||
'''
|
||||
Ensure an update is uninstalled from the minion
|
||||
|
||||
Args:
|
||||
|
||||
name(str):
|
||||
Name of the Windows KB ("KB123456")
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
KB123456:
|
||||
wusa.uninstalled
|
||||
'''
|
||||
ret = {'name': name,
|
||||
'changes': {},
|
||||
'result': False,
|
||||
'comment': ''}
|
||||
|
||||
# Is the KB already uninstalled
|
||||
if not __salt__['wusa.is_installed'](name):
|
||||
ret['result'] = True
|
||||
ret['comment'] = '{0} already uninstalled'.format(name)
|
||||
return ret
|
||||
|
||||
# Check for test=True
|
||||
if __opts__['test'] is True:
|
||||
ret['result'] = None
|
||||
ret['comment'] = '{0} would be uninstalled'.format(name)
|
||||
ret['result'] = None
|
||||
return ret
|
||||
|
||||
# Uninstall the KB
|
||||
__salt__['wusa.uninstall'](name)
|
||||
|
||||
# Verify successful uninstall
|
||||
if not __salt__['wusa.is_installed'](name):
|
||||
ret['comment'] = '{0} was uninstalled'.format(name)
|
||||
ret['changes'] = {'old': True, 'new': False}
|
||||
ret['result'] = True
|
||||
else:
|
||||
ret['comment'] = '{0} failed to uninstall'.format(name)
|
||||
|
||||
return ret
|
||||
|
|
190
tests/unit/modules/test_win_wusa.py
Normal file
190
tests/unit/modules/test_win_wusa.py
Normal file
|
@ -0,0 +1,190 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
'''
|
||||
Test the win_wusa execution module
|
||||
'''
|
||||
|
||||
# Import Python Libs
|
||||
from __future__ import absolute_import, unicode_literals, print_function
|
||||
|
||||
# Import Salt Testing Libs
|
||||
from tests.support.mixins import LoaderModuleMockMixin
|
||||
from tests.support.mock import NO_MOCK, NO_MOCK_REASON, patch, MagicMock
|
||||
from tests.support.unit import TestCase, skipIf
|
||||
|
||||
# Import Salt Libs
|
||||
import salt.utils.platform
|
||||
import salt.modules.win_wusa as win_wusa
|
||||
from salt.exceptions import CommandExecutionError
|
||||
|
||||
|
||||
@skipIf(NO_MOCK, NO_MOCK_REASON)
|
||||
@skipIf(not salt.utils.platform.is_windows(), 'System is not Windows')
|
||||
class WinWusaTestCase(TestCase, LoaderModuleMockMixin):
|
||||
'''
|
||||
test the functions in the win_wusa execution module
|
||||
'''
|
||||
def setup_loader_modules(self):
|
||||
return {win_wusa: {}}
|
||||
|
||||
def test_is_installed_false(self):
|
||||
'''
|
||||
test is_installed function when the KB is not installed
|
||||
'''
|
||||
mock_retcode = MagicMock(return_value=1)
|
||||
with patch.dict(win_wusa.__salt__, {'cmd.retcode': mock_retcode}):
|
||||
self.assertFalse(win_wusa.is_installed('KB123456'))
|
||||
|
||||
def test_is_installed_true(self):
|
||||
'''
|
||||
test is_installed function when the KB is installed
|
||||
'''
|
||||
mock_retcode = MagicMock(return_value=0)
|
||||
with patch.dict(win_wusa.__salt__, {'cmd.retcode': mock_retcode}):
|
||||
self.assertTrue(win_wusa.is_installed('KB123456'))
|
||||
|
||||
def test_list(self):
|
||||
'''
|
||||
test list function
|
||||
'''
|
||||
ret = {'pid': 1,
|
||||
'retcode': 0,
|
||||
'stderr': '',
|
||||
'stdout': '[{"HotFixID": "KB123456"}, '
|
||||
'{"HotFixID": "KB123457"}]'}
|
||||
mock_all = MagicMock(return_value=ret)
|
||||
with patch.dict(win_wusa.__salt__, {'cmd.run_all': mock_all}):
|
||||
expected = ['KB123456', 'KB123457']
|
||||
returned = win_wusa.list()
|
||||
self.assertListEqual(expected, returned)
|
||||
|
||||
def test_install(self):
|
||||
'''
|
||||
test install function
|
||||
'''
|
||||
mock_retcode = MagicMock(return_value=0)
|
||||
path = 'C:\\KB123456.msu'
|
||||
with patch.dict(win_wusa.__salt__, {'cmd.retcode': mock_retcode}):
|
||||
self.assertTrue(win_wusa.install(path))
|
||||
mock_retcode.assert_called_once_with(
|
||||
['wusa.exe', path, '/quiet', '/norestart'], ignore_retcode=True)
|
||||
|
||||
def test_install_restart(self):
|
||||
'''
|
||||
test install function with restart=True
|
||||
'''
|
||||
mock_retcode = MagicMock(return_value=0)
|
||||
path = 'C:\\KB123456.msu'
|
||||
with patch.dict(win_wusa.__salt__, {'cmd.retcode': mock_retcode}):
|
||||
self.assertTrue(win_wusa.install(path, restart=True))
|
||||
mock_retcode.assert_called_once_with(
|
||||
['wusa.exe', path, '/quiet', '/forcerestart'], ignore_retcode=True)
|
||||
|
||||
def test_install_already_installed(self):
|
||||
'''
|
||||
test install function when KB already installed
|
||||
'''
|
||||
mock_retcode = MagicMock(return_value=2359302)
|
||||
path = 'C:\\KB123456.msu'
|
||||
name = 'KB123456.msu'
|
||||
with patch.dict(win_wusa.__salt__, {'cmd.retcode': mock_retcode}):
|
||||
with self.assertRaises(CommandExecutionError) as excinfo:
|
||||
win_wusa.install(path)
|
||||
mock_retcode.assert_called_once_with(
|
||||
['wusa.exe', path, '/quiet', '/norestart'], ignore_retcode=True)
|
||||
self.assertEqual('{0} is already installed'.format(name),
|
||||
excinfo.exception.strerror)
|
||||
|
||||
def test_install_error_87(self):
|
||||
'''
|
||||
test install function when error 87 returned
|
||||
'''
|
||||
mock_retcode = MagicMock(return_value=87)
|
||||
path = 'C:\\KB123456.msu'
|
||||
with patch.dict(win_wusa.__salt__, {'cmd.retcode': mock_retcode}):
|
||||
with self.assertRaises(CommandExecutionError) as excinfo:
|
||||
win_wusa.install(path)
|
||||
mock_retcode.assert_called_once_with(
|
||||
['wusa.exe', path, '/quiet', '/norestart'], ignore_retcode=True)
|
||||
self.assertEqual('Unknown error', excinfo.exception.strerror)
|
||||
|
||||
def test_install_error_other(self):
|
||||
'''
|
||||
test install function on other unknown error
|
||||
'''
|
||||
mock_retcode = MagicMock(return_value=1234)
|
||||
path = 'C:\\KB123456.msu'
|
||||
with patch.dict(win_wusa.__salt__, {'cmd.retcode': mock_retcode}):
|
||||
with self.assertRaises(CommandExecutionError) as excinfo:
|
||||
win_wusa.install(path)
|
||||
mock_retcode.assert_called_once_with(
|
||||
['wusa.exe', path, '/quiet', '/norestart'], ignore_retcode=True)
|
||||
self.assertEqual('Unknown error: 1234', excinfo.exception.strerror)
|
||||
|
||||
def test_uninstall_kb(self):
|
||||
'''
|
||||
test uninstall function passing kb name
|
||||
'''
|
||||
mock_retcode = MagicMock(return_value=0)
|
||||
kb = 'KB123456'
|
||||
with patch.dict(win_wusa.__salt__, {'cmd.retcode': mock_retcode}), \
|
||||
patch("os.path.exists", MagicMock(return_value=False)):
|
||||
self.assertTrue(win_wusa.uninstall(kb))
|
||||
mock_retcode.assert_called_once_with(
|
||||
['wusa.exe', '/uninstall', '/quiet', '/kb:{0}'.format(kb[2:]), '/norestart'],
|
||||
ignore_retcode=True)
|
||||
|
||||
def test_uninstall_path(self):
|
||||
'''
|
||||
test uninstall function passing full path to .msu file
|
||||
'''
|
||||
mock_retcode = MagicMock(return_value=0)
|
||||
path = 'C:\\KB123456.msu'
|
||||
with patch.dict(win_wusa.__salt__, {'cmd.retcode': mock_retcode}), \
|
||||
patch("os.path.exists", MagicMock(return_value=True)):
|
||||
self.assertTrue(win_wusa.uninstall(path))
|
||||
mock_retcode.assert_called_once_with(
|
||||
['wusa.exe', '/uninstall', '/quiet', path, '/norestart'],
|
||||
ignore_retcode=True)
|
||||
|
||||
def test_uninstall_path_restart(self):
|
||||
'''
|
||||
test uninstall function with full path and restart=True
|
||||
'''
|
||||
mock_retcode = MagicMock(return_value=0)
|
||||
path = 'C:\\KB123456.msu'
|
||||
with patch.dict(win_wusa.__salt__, {'cmd.retcode': mock_retcode}), \
|
||||
patch("os.path.exists", MagicMock(return_value=True)):
|
||||
self.assertTrue(win_wusa.uninstall(path, restart=True))
|
||||
mock_retcode.assert_called_once_with(
|
||||
['wusa.exe', '/uninstall', '/quiet', path, '/forcerestart'],
|
||||
ignore_retcode=True)
|
||||
|
||||
def test_uninstall_already_uninstalled(self):
|
||||
'''
|
||||
test uninstall function when KB already uninstalled
|
||||
'''
|
||||
mock_retcode = MagicMock(return_value=2359303)
|
||||
kb = 'KB123456'
|
||||
with patch.dict(win_wusa.__salt__, {'cmd.retcode': mock_retcode}):
|
||||
with self.assertRaises(CommandExecutionError) as excinfo:
|
||||
win_wusa.uninstall(kb)
|
||||
mock_retcode.assert_called_once_with(
|
||||
['wusa.exe', '/uninstall', '/quiet', '/kb:{0}'.format(kb[2:]), '/norestart'],
|
||||
ignore_retcode=True)
|
||||
self.assertEqual('{0} not installed'.format(kb),
|
||||
excinfo.exception.strerror)
|
||||
|
||||
def test_uninstall_path_error_other(self):
|
||||
'''
|
||||
test uninstall function with unknown error
|
||||
'''
|
||||
mock_retcode = MagicMock(return_value=1234)
|
||||
path = 'C:\\KB123456.msu'
|
||||
with patch.dict(win_wusa.__salt__, {'cmd.retcode': mock_retcode}), \
|
||||
patch("os.path.exists", MagicMock(return_value=True)), \
|
||||
self.assertRaises(CommandExecutionError) as excinfo:
|
||||
win_wusa.uninstall(path)
|
||||
mock_retcode.assert_called_once_with(
|
||||
['wusa.exe', '/uninstall', '/quiet', path, '/norestart'],
|
||||
ignore_retcode=True)
|
||||
self.assertEqual('Unknown error: 1234', excinfo.exception.strerror)
|
169
tests/unit/states/test_win_wusa.py
Normal file
169
tests/unit/states/test_win_wusa.py
Normal file
|
@ -0,0 +1,169 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Import Python libs
|
||||
from __future__ import absolute_import, unicode_literals, print_function
|
||||
|
||||
# Import Salt Libs
|
||||
import salt.states.win_wusa as wusa
|
||||
from salt.exceptions import SaltInvocationError
|
||||
|
||||
# Import Salt Testing Libs
|
||||
from tests.support.mixins import LoaderModuleMockMixin
|
||||
from tests.support.unit import TestCase
|
||||
from tests.support.mock import MagicMock, patch
|
||||
|
||||
|
||||
class WinWusaTestCase(TestCase, LoaderModuleMockMixin):
|
||||
'''
|
||||
test the function in the win_wusa state module
|
||||
'''
|
||||
kb = 'KB123456'
|
||||
|
||||
def setup_loader_modules(self):
|
||||
return {wusa: {'__opts__': {'test': False},
|
||||
'__env__': 'base'}}
|
||||
|
||||
def test_installed_no_source(self):
|
||||
'''
|
||||
test wusa.installed without passing source
|
||||
'''
|
||||
with self.assertRaises(SaltInvocationError) as excinfo:
|
||||
wusa.installed(name='KB123456', source=None)
|
||||
|
||||
self.assertEqual(excinfo.exception.strerror,
|
||||
'Must specify a "source" file to install')
|
||||
|
||||
def test_installed_existing(self):
|
||||
'''
|
||||
test wusa.installed when the kb is already installed
|
||||
'''
|
||||
mock_installed = MagicMock(return_value=True)
|
||||
with patch.dict(wusa.__salt__, {'wusa.is_installed': mock_installed}):
|
||||
returned = wusa.installed(name=self.kb,
|
||||
source='salt://{0}.msu'.format(self.kb))
|
||||
expected = {'changes': {},
|
||||
'comment': '{0} already installed'.format(self.kb),
|
||||
'name': self.kb,
|
||||
'result': True}
|
||||
self.assertDictEqual(expected, returned)
|
||||
|
||||
def test_installed_test_true(self):
|
||||
'''
|
||||
test wusa.installed with test=True
|
||||
'''
|
||||
mock_installed = MagicMock(return_value=False)
|
||||
with patch.dict(wusa.__salt__, {'wusa.is_installed': mock_installed}), \
|
||||
patch.dict(wusa.__opts__, {'test': True}):
|
||||
returned = wusa.installed(name=self.kb,
|
||||
source='salt://{0}.msu'.format(self.kb))
|
||||
expected = {'changes': {},
|
||||
'comment': '{0} would be installed'.format(self.kb),
|
||||
'name': self.kb,
|
||||
'result': None}
|
||||
self.assertDictEqual(expected, returned)
|
||||
|
||||
def test_installed_cache_fail(self):
|
||||
'''
|
||||
test wusa.install when it fails to cache the file
|
||||
'''
|
||||
mock_installed = MagicMock(return_value=False)
|
||||
mock_cache = MagicMock(return_value='')
|
||||
with patch.dict(wusa.__salt__, {'wusa.is_installed': mock_installed,
|
||||
'cp.cache_file': mock_cache}):
|
||||
returned = wusa.installed(name=self.kb,
|
||||
source='salt://{0}.msu'.format(self.kb))
|
||||
expected = {'changes': {},
|
||||
'comment': 'Unable to cache salt://{0}.msu from '
|
||||
'saltenv "base"'.format(self.kb),
|
||||
'name': self.kb,
|
||||
'result': False}
|
||||
self.assertDictEqual(expected, returned)
|
||||
|
||||
def test_installed(self):
|
||||
'''
|
||||
test wusa.installed assuming success
|
||||
'''
|
||||
mock_installed = MagicMock(side_effect=[False, True])
|
||||
mock_cache = MagicMock(return_value='C:\\{0}.msu'.format(self.kb))
|
||||
with patch.dict(wusa.__salt__, {'wusa.is_installed': mock_installed,
|
||||
'cp.cache_file': mock_cache,
|
||||
'wusa.install': MagicMock()}):
|
||||
returned = wusa.installed(name=self.kb,
|
||||
source='salt://{0}.msu'.format(self.kb))
|
||||
expected = {'changes': {'new': True, 'old': False},
|
||||
'comment': '{0} was installed'.format(self.kb),
|
||||
'name': self.kb,
|
||||
'result': True}
|
||||
self.assertDictEqual(expected, returned)
|
||||
|
||||
def test_installed_failed(self):
|
||||
'''
|
||||
test wusa.installed with a failure
|
||||
'''
|
||||
mock_installed = MagicMock(side_effect=[False, False])
|
||||
mock_cache = MagicMock(return_value='C:\\{0}.msu'.format(self.kb))
|
||||
with patch.dict(wusa.__salt__, {'wusa.is_installed': mock_installed,
|
||||
'cp.cache_file': mock_cache,
|
||||
'wusa.install': MagicMock()}):
|
||||
returned = wusa.installed(name=self.kb,
|
||||
source='salt://{0}.msu'.format(self.kb))
|
||||
expected = {'changes': {},
|
||||
'comment': '{0} failed to install'.format(self.kb),
|
||||
'name': self.kb,
|
||||
'result': False}
|
||||
self.assertDictEqual(expected, returned)
|
||||
|
||||
def test_uninstalled_non_existing(self):
|
||||
'''
|
||||
test wusa.uninstalled when the kb is not installed
|
||||
'''
|
||||
mock_installed = MagicMock(return_value=False)
|
||||
with patch.dict(wusa.__salt__, {'wusa.is_installed': mock_installed}):
|
||||
returned = wusa.uninstalled(name=self.kb)
|
||||
expected = {'changes': {},
|
||||
'comment': '{0} already uninstalled'.format(self.kb),
|
||||
'name': self.kb,
|
||||
'result': True}
|
||||
self.assertDictEqual(expected, returned)
|
||||
|
||||
def test_uninstalled_test_true(self):
|
||||
'''
|
||||
test wusa.uninstalled with test=True
|
||||
'''
|
||||
mock_installed = MagicMock(return_value=True)
|
||||
with patch.dict(wusa.__salt__, {'wusa.is_installed': mock_installed}), \
|
||||
patch.dict(wusa.__opts__, {'test': True}):
|
||||
returned = wusa.uninstalled(name=self.kb)
|
||||
expected = {'changes': {},
|
||||
'comment': '{0} would be uninstalled'.format(self.kb),
|
||||
'name': self.kb,
|
||||
'result': None}
|
||||
self.assertDictEqual(expected, returned)
|
||||
|
||||
def test_uninstalled(self):
|
||||
'''
|
||||
test wusa.uninstalled assuming success
|
||||
'''
|
||||
mock_installed = MagicMock(side_effect=[True, False])
|
||||
with patch.dict(wusa.__salt__, {'wusa.is_installed': mock_installed,
|
||||
'wusa.uninstall': MagicMock()}):
|
||||
returned = wusa.uninstalled(name=self.kb)
|
||||
expected = {'changes': {'new': False, 'old': True},
|
||||
'comment': '{0} was uninstalled'.format(self.kb),
|
||||
'name': self.kb,
|
||||
'result': True}
|
||||
self.assertDictEqual(expected, returned)
|
||||
|
||||
def test_uninstalled_failed(self):
|
||||
'''
|
||||
test wusa.uninstalled with a failure
|
||||
'''
|
||||
mock_installed = MagicMock(side_effect=[True, True])
|
||||
with patch.dict(wusa.__salt__, {'wusa.is_installed': mock_installed,
|
||||
'wusa.uninstall': MagicMock()}):
|
||||
returned = wusa.uninstalled(name=self.kb)
|
||||
expected = {'changes': {},
|
||||
'comment': '{0} failed to uninstall'.format(self.kb),
|
||||
'name': self.kb,
|
||||
'result': False}
|
||||
self.assertDictEqual(expected, returned)
|
Loading…
Add table
Reference in a new issue