From 3ccd6d3b3fd74c4a51aee0680c28f1d4bfae9329 Mon Sep 17 00:00:00 2001 From: twangboy Date: Thu, 6 Feb 2020 11:13:58 -0700 Subject: [PATCH 01/14] Update pip and setuptools --- pkg/windows/req_pip.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/windows/req_pip.txt b/pkg/windows/req_pip.txt index bc2797de3110..9090650e6846 100644 --- a/pkg/windows/req_pip.txt +++ b/pkg/windows/req_pip.txt @@ -1,2 +1,2 @@ -pip==9.0.1 -setuptools==38.2.4 +pip==20.0.2 +setuptools==45.1.0 From 5b3ef25644ef585612db590bba2e0f8f3ddc3abd Mon Sep 17 00:00:00 2001 From: twangboy Date: Fri, 10 Apr 2020 13:16:29 -0600 Subject: [PATCH 02/14] Add `installed` option to Windows Update --- salt/modules/win_wua.py | 61 +++++++++++++++++++++- salt/utils/win_update.py | 41 ++++++++++++--- tests/unit/modules/test_win_wua.py | 0 tests/unit/utils/test_win_update.py | 79 +++++++++++++++++++++++++++++ 4 files changed, 173 insertions(+), 8 deletions(-) create mode 100644 tests/unit/modules/test_win_wua.py create mode 100644 tests/unit/utils/test_win_update.py diff --git a/salt/modules/win_wua.py b/salt/modules/win_wua.py index dad87182926a..3b21b03980c3 100644 --- a/salt/modules/win_wua.py +++ b/salt/modules/win_wua.py @@ -347,6 +347,7 @@ def list( severities=None, download=False, install=False, + online=True, ): """ .. versionadded:: 2017.7.0 @@ -468,7 +469,7 @@ def list( salt '*' win_wua.list categories=['Feature Packs','Windows 8.1'] summary=True """ # Create a Windows Update Agent instance - wua = salt.utils.win_update.WindowsUpdateAgent() + wua = salt.utils.win_update.WindowsUpdateAgent(online=online) # Search for Update updates = wua.available( @@ -495,6 +496,64 @@ def list( return ret +def installed(summary=False, kbs_only=False): + """ + Get a list of all updates that are currently installed on the system. + + .. note:: + This list may not necessarily match the Update History on the machine. + This will only show the updates that apply to the current build of + Windows. So, for example, the system may have shipped with Windows 10 + Build 1607. That machine received updates to the 1607 build. Later the + machine was upgraded to a newer feature release, 1803 for example. Then + more updates were applied. This will only return the updates applied to + the 1803 build and not those applied when the system was at the 1607 + build. + + .. versionadded:: Sodium + + Args: + summary (bool): Return a summary instead of a detailed list of updates. + ``True`` will return a Summary, ``False`` will return a detailed + list of installed updates. Default is ``False`` + + kbs_only (bool): Only return a list of KBs installed on the system. If + this parameter is passed, the ``summary`` parameter will be ignored. + Default is ``False`` + + Returns: + dict: (``kbs_only=False``) Returns a dictionary of either a Summary or a + detailed list of updates installed on the system. + list: (``kbs_only=True``) Returns a list of KBs installed on the system. + + CLI Examples: + + .. code-block:: bash + + # Get a detailed list of all applicable updates installed on the system + salt '*' win_wua.installed + + # Get a summary of all applicable updates installed on the system + salt '*' win_wua.installed summary=True online=False + + # Get a simple list of KBs installed on the system + salt '*' win_wua.installed kbs_only=True + """ + # Create a Windows Update Agent instance. Since we're only listing installed + # updates, there's no need to go online to update the Windows Update db + wua = salt.utils.win_update.WindowsUpdateAgent(online=False) + updates = wua.installed() # Get installed Updates objects + results = updates.list() # Convert to list + + if kbs_only: + list_kbs = set() + for item in results: + list_kbs.update(results[item]['KBs']) + return sorted(list_kbs) + + return updates.summary() if summary else results + + def download(names): """ .. versionadded:: 2017.7.0 diff --git a/salt/utils/win_update.py b/salt/utils/win_update.py index 7d3ee4b98ebd..40d7a460a01b 100644 --- a/salt/utils/win_update.py +++ b/salt/utils/win_update.py @@ -32,7 +32,7 @@ def __virtual__(): if not salt.utils.platform.is_windows(): - return False, "win_update: Not available on Windows" + return False, "win_update: Only available on Windows" if not HAS_PYWIN32: return False, "win_update: Missing pywin32" return __virtualname__ @@ -285,7 +285,7 @@ class WindowsUpdateAgent(object): -4292607995: "Reboot required: 0x00240005", } - def __init__(self): + def __init__(self, online=True): """ Initialize the session and load all updates into the ``_updates`` collection. This collection is used by the other class functions instead @@ -302,7 +302,7 @@ def __init__(self): # Create Collection for Updates self._updates = win32com.client.Dispatch("Microsoft.Update.UpdateColl") - self.refresh() + self.refresh(online=online) def updates(self): """ @@ -333,7 +333,7 @@ def updates(self): return updates - def refresh(self): + def refresh(self, online=True): """ Refresh the contents of the ``_updates`` collection. This gets all updates in the Windows Update system and loads them into the collection. @@ -352,6 +352,7 @@ def refresh(self): # Create searcher object searcher = self._session.CreateUpdateSearcher() + searcher.Online = online self._session.ClientApplicationID = "Salt: Load Updates" # Load all updates into the updates collection @@ -373,6 +374,32 @@ def refresh(self): self._updates = results.Updates + def installed(self): + """ + Gets a list of all updates available on the system that have the + ``IsInstalled`` attribute set to ``True``. + + Returns: + + Updates: An instance of Updates with the results. + + Code Example: + + .. code-block:: python + + import salt.utils.win_update + wua = salt.utils.win_update.WindowsUpdateAgent(online=False) + installed_updates = wua.installed() + """ + # https://msdn.microsoft.com/en-us/library/windows/desktop/aa386099(v=vs.85).aspx + updates = Updates() + + for update in self._updates: + if salt.utils.data.is_true(update.IsInstalled): + updates.updates.Add(update) + + return updates + def available( self, skip_hidden=True, @@ -445,7 +472,7 @@ def available( wua = salt.utils.win_update.WindowsUpdateAgent() # Gets all updates and shows a summary - updates = wua.available + updates = wua.available() updates.summary() # Get a list of Critical updates @@ -920,7 +947,7 @@ def uninstall(self, updates): log.debug("NeedsReboot: %s", ret["NeedsReboot"]) # Refresh the Updates Table - self.refresh() + self.refresh(online=False) reboot = {0: "Never Reboot", 1: "Always Reboot", 2: "Poss Reboot"} @@ -941,7 +968,7 @@ def uninstall(self, updates): return ret - # Found a differenct exception, Raise error + # Found a different exception, Raise error log.error("Uninstall Failed: %s", failure_code) raise CommandExecutionError(failure_code) diff --git a/tests/unit/modules/test_win_wua.py b/tests/unit/modules/test_win_wua.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/unit/utils/test_win_update.py b/tests/unit/utils/test_win_update.py new file mode 100644 index 000000000000..bb24268ccf8f --- /dev/null +++ b/tests/unit/utils/test_win_update.py @@ -0,0 +1,79 @@ +# -*- coding: utf-8 -*- + +# Import Python Libs +from __future__ import absolute_import, unicode_literals, print_function + +# Import Salt Testing Libs +from tests.support.mock import patch, MagicMock +from tests.support.unit import TestCase + +# Import Salt Libs +import salt.utils.win_update as win_update + + +# @skipIf(not salt.utils.platform.is_windows(), 'Only test on Windows systems') +class WinUpdateTestCase(TestCase): + ''' + Test cases for salt.utils.win_update + ''' + def test_installed_no_updates(self): + with patch('salt.utils.winapi.Com', autospec=True), \ + patch('win32com.client.Dispatch', autospec=True), \ + patch.object(win_update.WindowsUpdateAgent, 'refresh', autospec=True): + + wua = win_update.WindowsUpdateAgent(online=False) + wua._updates = [] + + installed_updates = wua.installed() + + assert installed_updates.updates.Add.call_count == 0 + + def test_installed_no_updates_installed(self): + with patch('salt.utils.winapi.Com', autospec=True), \ + patch('win32com.client.Dispatch', autospec=True), \ + patch.object(win_update.WindowsUpdateAgent, 'refresh', autospec=True): + wua = win_update.WindowsUpdateAgent(online=False) + + wua._updates = [ + MagicMock(IsInstalled=False), + MagicMock(IsInstalled=False), + MagicMock(IsInstalled=False), + ] + + installed_updates = wua.installed() + + assert installed_updates.updates.Add.call_count == 0 + + def test_installed_updates_all_installed(self): + with patch('salt.utils.winapi.Com', autospec=True), \ + patch('win32com.client.Dispatch', autospec=True), \ + patch.object(win_update.WindowsUpdateAgent, 'refresh', autospec=True): + wua = win_update.WindowsUpdateAgent(online=False) + + wua._updates = [ + MagicMock(IsInstalled=True), + MagicMock(IsInstalled=True), + MagicMock(IsInstalled=True), + ] + + installed_updates = wua.installed() + + assert installed_updates.updates.Add.call_count == 3 + + def test_installed_updates_some_installed(self): + with patch('salt.utils.winapi.Com', autospec=True), \ + patch('win32com.client.Dispatch', autospec=True), \ + patch.object(win_update.WindowsUpdateAgent, 'refresh', autospec=True): + wua = win_update.WindowsUpdateAgent(online=False) + + wua._updates = [ + MagicMock(IsInstalled=True), + MagicMock(IsInstalled=False), + MagicMock(IsInstalled=True), + MagicMock(IsInstalled=False), + MagicMock(IsInstalled=True), + ] + + installed_updates = wua.installed() + + assert installed_updates.updates.Add.call_count == 3 From bf5118bbca0f39800ecd311f913535819e282077 Mon Sep 17 00:00:00 2001 From: twangboy Date: Fri, 10 Apr 2020 13:28:09 -0600 Subject: [PATCH 03/14] Roll back pip changes --- pkg/windows/req_pip.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/windows/req_pip.txt b/pkg/windows/req_pip.txt index 9090650e6846..bc2797de3110 100644 --- a/pkg/windows/req_pip.txt +++ b/pkg/windows/req_pip.txt @@ -1,2 +1,2 @@ -pip==20.0.2 -setuptools==45.1.0 +pip==9.0.1 +setuptools==38.2.4 From 35de9f17a35fb6a7a419135994aecaf7791bc787 Mon Sep 17 00:00:00 2001 From: twangboy Date: Mon, 13 Apr 2020 19:15:19 -0600 Subject: [PATCH 04/14] Add tests for installed function --- tests/unit/modules/test_win_wua.py | 113 +++++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) diff --git a/tests/unit/modules/test_win_wua.py b/tests/unit/modules/test_win_wua.py index e69de29bb2d1..29bfdc127534 100644 --- a/tests/unit/modules/test_win_wua.py +++ b/tests/unit/modules/test_win_wua.py @@ -0,0 +1,113 @@ +# -*- coding: utf-8 -*- +""" +Test the win_wua execution module +""" +# Import Python Libs +from __future__ import absolute_import, print_function, unicode_literals + +# Import Salt Libs +import salt.modules.win_wua as win_wua +import salt.utils.platform +import salt.utils.win_update + +# Import Salt Testing Libs +from tests.support.mock import patch +from tests.support.unit import TestCase + +UPDATES_LIST = { + 'ca3bb521-a8ea-4e26-a563-2ad6e3108b9a': { + 'KBs': ['KB4481252'], + }, + '07609d43-d518-4e77-856e-d1b316d1b8a8': { + 'KBs': ['KB925673'], + }, + 'fbaa5360-a440-49d8-a3b6-0c4fc7ecaa19': { + 'KBs': ['KB4481252'], + }, + 'a873372b-7a5c-443c-8022-cd59a550bef4': { + 'KBs': ['KB3193497'], + }, + '14075cbe-822e-4004-963b-f50e08d45563': { + 'KBs': ['KB4540723'], + }, + 'd931e99c-4dda-4d39-9905-0f6a73f7195f': { + 'KBs': ['KB3193497'], + }, + 'afda9e11-44a0-4602-9e9b-423af11ecaed': { + 'KBs': ['KB4541329'], + }, + 'a0f997b1-1abe-4a46-941f-b37f732f9fbd': { + 'KBs': ['KB3193497'], + }, + 'eac02b09-d745-4891-b80f-400e0e5e4b6d': { + 'KBs': ['KB4052623'], + }, + '0689e74b-54d1-4f55-a916-96e3c737db90': { + 'KBs': ['KB890830'], + } +} +UPDATES_SUMMARY = { + 'Installed': 10, +} + + +class Updates(object): + @staticmethod + def list(): + return UPDATES_LIST + + @staticmethod + def summary(): + return UPDATES_SUMMARY + + +class WinWuaInstalledTestCase(TestCase): + """ + Test the functions in the win_wua.installed function + """ + def test_installed(self): + """ + Test installed function default + """ + expected = UPDATES_LIST + with patch('salt.utils.winapi.Com', autospec=True), \ + patch('win32com.client.Dispatch', autospec=True), \ + patch.object(salt.utils.win_update.WindowsUpdateAgent, + 'refresh', autospec=True), \ + patch.object(salt.utils.win_update, 'Updates', autospec=True, + return_value=Updates()): + result = win_wua.installed() + self.assertDictEqual(result, expected) + + def test_installed_summary(self): + """ + Test installed function with summary=True + """ + expected = UPDATES_SUMMARY + # Remove all updates that are not installed + with patch('salt.utils.winapi.Com', autospec=True), \ + patch('win32com.client.Dispatch', autospec=True), \ + patch.object(salt.utils.win_update.WindowsUpdateAgent, + 'refresh', autospec=True), \ + patch.object(salt.utils.win_update, 'Updates', autospec=True, + return_value=Updates()): + result = win_wua.installed(summary=True) + self.assertDictEqual(result, expected) + + def test_installed_kbs_only(self): + """ + Test installed function with kbs_only=True + """ + expected = set() + for update in UPDATES_LIST: + expected.update(UPDATES_LIST[update]['KBs']) + expected = sorted(expected) + # Remove all updates that are not installed + with patch('salt.utils.winapi.Com', autospec=True), \ + patch('win32com.client.Dispatch', autospec=True), \ + patch.object(salt.utils.win_update.WindowsUpdateAgent, + 'refresh', autospec=True), \ + patch.object(salt.utils.win_update, 'Updates', autospec=True, + return_value=Updates()): + result = win_wua.installed(kbs_only=True) + self.assertListEqual(result, expected) From b37704af3e152e5d62a075dab8cbc8d4a4065294 Mon Sep 17 00:00:00 2001 From: twangboy Date: Mon, 13 Apr 2020 19:37:32 -0600 Subject: [PATCH 05/14] Add versionadded to docs --- salt/modules/win_wua.py | 31 +++++++++++++++++++++++++------ salt/utils/win_update.py | 16 ++++++++++++++++ 2 files changed, 41 insertions(+), 6 deletions(-) diff --git a/salt/modules/win_wua.py b/salt/modules/win_wua.py index 3b21b03980c3..c0b2d3cc726d 100644 --- a/salt/modules/win_wua.py +++ b/salt/modules/win_wua.py @@ -109,6 +109,7 @@ def available( skip_reboot=False, categories=None, severities=None, + online=True, ): """ .. versionadded:: 2017.7.0 @@ -169,6 +170,12 @@ def available( * Critical * Important + online (bool): Tells the Windows Update Agent go online to update + its local update database. ``True`` will go online. ``False`` + will use the local update database as is. Default is ``False`` + + .. versionadded:: Sodium + Returns: dict: Returns a dict containing either a summary or a list of updates: @@ -228,7 +235,7 @@ def available( """ # Create a Windows Update Agent instance - wua = salt.utils.win_update.WindowsUpdateAgent() + wua = salt.utils.win_update.WindowsUpdateAgent(online=online) # Look for available updates = wua.available( @@ -246,7 +253,7 @@ def available( return updates.summary() if summary else updates.list() -def get(name, download=False, install=False): +def get(name, download=False, install=False, online=True): """ .. versionadded:: 2017.7.0 @@ -270,11 +277,17 @@ def get(name, download=False, install=False): first to see if the update exists, then set ``install=True`` to install the update. + online (bool): Tells the Windows Update Agent go online to update + its local update database. ``True`` will go online. ``False`` + will use the local update database as is. Default is ``False`` + + .. versionadded:: Sodium + Returns: dict: Returns a dict containing a list of updates that match the name if - download and install are both set to False. Should usually be a single - update, but can return multiple if a partial name is given. + download and install are both set to False. Should usually be a + single update, but can return multiple if a partial name is given. If download or install is set to true it will return the results of the operation. @@ -320,7 +333,7 @@ def get(name, download=False, install=False): salt '*' win_wua.get 'Microsoft Camera Codec Pack' """ # Create a Windows Update Agent instance - wua = salt.utils.win_update.WindowsUpdateAgent() + wua = salt.utils.win_update.WindowsUpdateAgent(online=online) # Search for Update updates = wua.search(name) @@ -411,6 +424,12 @@ def list( * Critical * Important + online (bool): Tells the Windows Update Agent go online to update + its local update database. ``True`` will go online. ``False`` + will use the local update database as is. Default is ``False`` + + .. versionadded:: Sodium + Returns: dict: Returns a dict containing either a summary or a list of updates: @@ -534,7 +553,7 @@ def installed(summary=False, kbs_only=False): salt '*' win_wua.installed # Get a summary of all applicable updates installed on the system - salt '*' win_wua.installed summary=True online=False + salt '*' win_wua.installed summary=True # Get a simple list of KBs installed on the system salt '*' win_wua.installed kbs_only=True diff --git a/salt/utils/win_update.py b/salt/utils/win_update.py index 40d7a460a01b..467e08695875 100644 --- a/salt/utils/win_update.py +++ b/salt/utils/win_update.py @@ -291,6 +291,14 @@ def __init__(self, online=True): collection. This collection is used by the other class functions instead of querying Windows update (expensive). + Args: + + online (bool): Tells the Windows Update Agent go online to update + its local update database. ``True`` will go online. ``False`` + will use the local update database as is. Default is ``False`` + + .. versionadded:: Sodium + Need to look at the possibility of loading this into ``__context__`` """ # Initialize the PyCom system @@ -339,6 +347,14 @@ def refresh(self, online=True): updates in the Windows Update system and loads them into the collection. This is the part that is slow. + Args: + + online (bool): Tells the Windows Update Agent go online to update + its local update database. ``True`` will go online. ``False`` + will use the local update database as is. Default is ``False`` + + .. versionadded:: Sodium + Code Example: .. code-block:: python From ccd0dec2fba9cced76c8e0f2521abca809ece6cb Mon Sep 17 00:00:00 2001 From: twangboy Date: Tue, 14 Apr 2020 12:04:32 -0600 Subject: [PATCH 06/14] Fix some issues with docs --- salt/modules/win_wua.py | 30 ++++++++++----------- salt/utils/win_update.py | 56 +++++++++++++++++++++------------------- 2 files changed, 44 insertions(+), 42 deletions(-) diff --git a/salt/modules/win_wua.py b/salt/modules/win_wua.py index c0b2d3cc726d..dff349522944 100644 --- a/salt/modules/win_wua.py +++ b/salt/modules/win_wua.py @@ -126,8 +126,8 @@ def available( Include driver updates in the results. Default is ``True`` summary (bool): - - True: Return a summary of updates available for each category. - - False (default): Return a detailed list of available updates. + - ``True``: Return a summary of updates available for each category. + - ``False`` (default): Return a detailed list of available updates. skip_installed (bool): Skip updates that are already installed. Default is ``True`` @@ -149,7 +149,7 @@ def available( * Critical Updates * Definition Updates - * Drivers (make sure you set drivers=True) + * Drivers (make sure you set ``drivers=True``) * Feature Packs * Security Updates * Update Rollups @@ -377,8 +377,8 @@ def list( Include driver updates in the results. Default is ``False`` summary (bool): - - True: Return a summary of updates available for each category. - - False (default): Return a detailed list of available updates. + - ``True``: Return a summary of updates available for each category. + - ``False`` (default): Return a detailed list of available updates. skip_installed (bool): Skip installed updates in the results. Default is ``True`` @@ -403,7 +403,7 @@ def list( * Critical Updates * Definition Updates - * Drivers (make sure you set drivers=True) + * Drivers (make sure you set ``drivers=True``) * Feature Packs * Security Updates * Update Rollups @@ -578,7 +578,7 @@ def download(names): .. versionadded:: 2017.7.0 Downloads updates that match the list of passed identifiers. It's easier to - use this function by using list_updates and setting install=True. + use this function by using list_updates and setting ``download=True``. Args: @@ -620,7 +620,7 @@ def download(names): if updates.count() > len(names): raise CommandExecutionError( - "Multiple updates found, names need to be " "more specific" + "Multiple updates found, names need to be more specific" ) return wua.download(updates) @@ -631,7 +631,7 @@ def install(names): .. versionadded:: 2017.7.0 Installs updates that match the list of identifiers. It may be easier to use - the list_updates function and set install=True. + the list_updates function and set ``install=True``. Args: @@ -673,7 +673,7 @@ def install(names): if updates.count() > len(names): raise CommandExecutionError( - "Multiple updates found, names need to be " "more specific" + "Multiple updates found, names need to be more specific" ) return wua.install(updates) @@ -882,7 +882,7 @@ def set_wu_settings( } if day not in days: ret["Comment"] = ( - "Day needs to be one of the following: Everyday," + "Day needs to be one of the following: Everyday, " "Monday, Tuesday, Wednesday, Thursday, Friday, " "Saturday" ) @@ -902,15 +902,15 @@ def set_wu_settings( # treat it as an integer if not isinstance(time, six.string_types): ret["Comment"] = ( - "Time argument needs to be a string; it may need to" + "Time argument needs to be a string; it may need to " "be quoted. Passed {0}. Time not set.".format(time) ) ret["Success"] = False # Check for colon in the time elif ":" not in time: ret["Comment"] = ( - "Time argument needs to be in 00:00 format." - " Passed {0}. Time not set.".format(time) + "Time argument needs to be in 00:00 format. " + "Passed {0}. Time not set.".format(time) ) ret["Success"] = False else: @@ -1097,7 +1097,7 @@ def get_needs_reboot(): Returns: - bool: True if the system requires a reboot, otherwise False + bool: ``True`` if the system requires a reboot, otherwise ``False`` CLI Examples: diff --git a/salt/utils/win_update.py b/salt/utils/win_update.py index 467e08695875..b0c6117ffce6 100644 --- a/salt/utils/win_update.py +++ b/salt/utils/win_update.py @@ -251,9 +251,7 @@ def summary(self): class WindowsUpdateAgent(object): """ Class for working with the Windows update agent - """ - # Error codes found at the following site: # https://msdn.microsoft.com/en-us/library/windows/desktop/hh968413(v=vs.85).aspx # https://technet.microsoft.com/en-us/library/cc720442(v=ws.10).aspx @@ -433,21 +431,21 @@ def available( Args: - skip_hidden (bool): Skip hidden updates. Default is True + skip_hidden (bool): Skip hidden updates. Default is ``True`` - skip_installed (bool): Skip installed updates. Default is True + skip_installed (bool): Skip installed updates. Default is ``True`` - skip_mandatory (bool): Skip mandatory updates. Default is False + skip_mandatory (bool): Skip mandatory updates. Default is ``False`` skip_reboot (bool): Skip updates that can or do require reboot. - Default is False + Default is ``False`` - software (bool): Include software updates. Default is True + software (bool): Include software updates. Default is ``True`` - drivers (bool): Include driver updates. Default is True + drivers (bool): Include driver updates. Default is ``True`` categories (list): Include updates that have these categories. - Default is none (all categories). + Default is none (all categories). Categories include the following: @@ -466,15 +464,17 @@ def available( * Windows Defender severities (list): Include updates that have these severities. - Default is none (all severities). + Default is none (all severities). Severities include the following: * Critical * Important - .. note:: All updates are either software or driver updates. If both - ``software`` and ``drivers`` is False, nothing will be returned. + .. note:: + + All updates are either software or driver updates. If both + ``software`` and ``drivers`` is ``False``, nothing will be returned. Returns: @@ -546,10 +546,10 @@ def search(self, search_string): Args: search_string (str, list): The search string to use to find the - update. This can be the GUID or KB of the update (preferred). It can - also be the full Title of the update or any part of the Title. A - partial Title search is less specific and can return multiple - results. + update. This can be the GUID or KB of the update (preferred). It + can also be the full Title of the update or any part of the + Title. A partial Title search is less specific and can return + multiple results. Returns: Updates: An instance of Updates with the results of the search @@ -611,8 +611,8 @@ def download(self, updates): Args: - updates (Updates): An instance of the Updates class containing a - the updates to be downloaded. + updates (Updates): An instance of the Updates class containing a the + updates to be downloaded. Returns: dict: A dictionary containing the results of the download @@ -725,7 +725,7 @@ def install(self, updates): Args: updates (Updates): An instance of the Updates class containing a - the updates to be installed. + the updates to be installed. Returns: dict: A dictionary containing the results of the installation @@ -832,19 +832,21 @@ def uninstall(self, updates): Uninstall the updates passed in the updates collection. Load the updates collection using the ``search`` or ``available`` functions. - .. note:: Starting with Windows 10 the Windows Update Agent is unable to - uninstall updates. An ``Uninstall Not Allowed`` error is returned. If - this error is encountered this function will instead attempt to use - ``dism.exe`` to perform the uninstallation. ``dism.exe`` may fail to - to find the KB number for the package. In that case, removal will fail. + .. note:: + Starting with Windows 10 the Windows Update Agent is unable to + uninstall updates. An ``Uninstall Not Allowed`` error is returned. + If this error is encountered this function will instead attempt to + use ``dism.exe`` to perform the un-installation. ``dism.exe`` may + fail to to find the KB number for the package. In that case, removal + will fail. Args: updates (Updates): An instance of the Updates class containing a - the updates to be uninstalled. + the updates to be uninstalled. Returns: - dict: A dictionary containing the results of the uninstallation + dict: A dictionary containing the results of the un-installation Code Example: @@ -1055,7 +1057,7 @@ def needs_reboot(): Returns: - bool: True if the system requires a reboot, False if not + bool: ``True`` if the system requires a reboot, ``False`` if not CLI Examples: From 837459a6f2adc1090258f8ec2fd085b54eaa08ec Mon Sep 17 00:00:00 2001 From: twangboy Date: Tue, 14 Apr 2020 12:21:44 -0600 Subject: [PATCH 07/14] Update changelog --- CHANGELOG.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b09e43ad2805..2926b171dc45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,20 @@ This project versioning is _similar_ to [Semantic Versioning](https://semver.org Versions are `MAJOR.PATCH`. ## 3001 - Sodium +## 3001 - Sodium [yyyy-mm-dd] + +### Removed + +### Deprecated + +### Changed + +### Fixed + +### Added +- [#56637](https://github.com/saltstack/salt/pull/56637) - Add ``win_wua.installed`` to the ``win_wua`` execution module + +## 3000.1 ### Removed From 448cfa0104ff1789eecf3031df26af0910650e19 Mon Sep 17 00:00:00 2001 From: twangboy Date: Tue, 14 Apr 2020 12:54:52 -0600 Subject: [PATCH 08/14] Fix some issues with docs --- salt/modules/win_wua.py | 202 ++++++++++++++++++++++----------------- salt/utils/win_update.py | 130 ++++++++++++++----------- 2 files changed, 188 insertions(+), 144 deletions(-) diff --git a/salt/modules/win_wua.py b/salt/modules/win_wua.py index dff349522944..eb1b9b593cd6 100644 --- a/salt/modules/win_wua.py +++ b/salt/modules/win_wua.py @@ -170,9 +170,10 @@ def available( * Critical * Important - online (bool): Tells the Windows Update Agent go online to update - its local update database. ``True`` will go online. ``False`` - will use the local update database as is. Default is ``False`` + online (bool): + Tells the Windows Update Agent go online to update its local update + database. ``True`` will go online. ``False`` will use the local + update database as is. Default is ``False`` .. versionadded:: Sodium @@ -182,33 +183,35 @@ def available( .. code-block:: cfg - List of Updates: - {'': {'Title': , - 'KB': <KB>, - 'GUID': <the globally unique identifier for the update> - 'Description': <description>, - 'Downloaded': <has the update been downloaded>, - 'Installed': <has the update been installed>, - 'Mandatory': <is the update mandatory>, - 'UserInput': <is user input required>, - 'EULAAccepted': <has the EULA been accepted>, - 'Severity': <update severity>, - 'NeedsReboot': <is the update installed and awaiting reboot>, - 'RebootBehavior': <will the update require a reboot>, - 'Categories': [ '<category 1>', - '<category 2>', - ...] - } - } + Dict of Updates: + {'<GUID>': { + 'Title': <title>, + 'KB': <KB>, + 'GUID': <the globally unique identifier for the update>, + 'Description': <description>, + 'Downloaded': <has the update been downloaded>, + 'Installed': <has the update been installed>, + 'Mandatory': <is the update mandatory>, + 'UserInput': <is user input required>, + 'EULAAccepted': <has the EULA been accepted>, + 'Severity': <update severity>, + 'NeedsReboot': <is the update installed and awaiting reboot>, + 'RebootBehavior': <will the update require a reboot>, + 'Categories': [ + '<category 1>', + '<category 2>', + ... ] + }} Summary of Updates: {'Total': <total number of updates returned>, 'Available': <updates that are not downloaded or installed>, 'Downloaded': <updates that are downloaded but not installed>, 'Installed': <updates installed (usually 0 unless installed=True)>, - 'Categories': { <category 1>: <total for that category>, - <category 2>: <total for category 2>, - ... } + 'Categories': { + <category 1>: <total for that category>, + <category 2>: <total for category 2>, + ... } } CLI Examples: @@ -277,15 +280,17 @@ def get(name, download=False, install=False, online=True): first to see if the update exists, then set ``install=True`` to install the update. - online (bool): Tells the Windows Update Agent go online to update - its local update database. ``True`` will go online. ``False`` - will use the local update database as is. Default is ``False`` + online (bool): + Tells the Windows Update Agent go online to update its local update + database. ``True`` will go online. ``False`` will use the local + update database as is. Default is ``False`` .. versionadded:: Sodium Returns: - dict: Returns a dict containing a list of updates that match the name if + dict: + Returns a dict containing a list of updates that match the name if download and install are both set to False. Should usually be a single update, but can return multiple if a partial name is given. @@ -294,24 +299,25 @@ def get(name, download=False, install=False, online=True): .. code-block:: cfg - List of Updates: - {'<GUID>': {'Title': <title>, - 'KB': <KB>, - 'GUID': <the globally unique identifier for the update> - 'Description': <description>, - 'Downloaded': <has the update been downloaded>, - 'Installed': <has the update been installed>, - 'Mandatory': <is the update mandatory>, - 'UserInput': <is user input required>, - 'EULAAccepted': <has the EULA been accepted>, - 'Severity': <update severity>, - 'NeedsReboot': <is the update installed and awaiting reboot>, - 'RebootBehavior': <will the update require a reboot>, - 'Categories': [ '<category 1>', - '<category 2>', - ...] - } - } + Dict of Updates: + {'<GUID>': { + 'Title': <title>, + 'KB': <KB>, + 'GUID': <the globally unique identifier for the update>, + 'Description': <description>, + 'Downloaded': <has the update been downloaded>, + 'Installed': <has the update been installed>, + 'Mandatory': <is the update mandatory>, + 'UserInput': <is user input required>, + 'EULAAccepted': <has the EULA been accepted>, + 'Severity': <update severity>, + 'NeedsReboot': <is the update installed and awaiting reboot>, + 'RebootBehavior': <will the update require a reboot>, + 'Categories': [ + '<category 1>', + '<category 2>', + ... ] + }} CLI Examples: @@ -365,8 +371,9 @@ def list( """ .. versionadded:: 2017.7.0 - Returns a detailed list of available updates or a summary. If download or - install is True the same list will be downloaded and/or installed. + Returns a detailed list of available updates or a summary. If ``download`` + or ``install`` is ``True`` the same list will be downloaded and/or + installed. Args: @@ -424,9 +431,10 @@ def list( * Critical * Important - online (bool): Tells the Windows Update Agent go online to update - its local update database. ``True`` will go online. ``False`` - will use the local update database as is. Default is ``False`` + online (bool): + Tells the Windows Update Agent go online to update its local update + database. ``True`` will go online. ``False`` will use the local + update database as is. Default is ``False`` .. versionadded:: Sodium @@ -436,33 +444,35 @@ def list( .. code-block:: cfg - List of Updates: - {'<GUID>': {'Title': <title>, - 'KB': <KB>, - 'GUID': <the globally unique identifier for the update> - 'Description': <description>, - 'Downloaded': <has the update been downloaded>, - 'Installed': <has the update been installed>, - 'Mandatory': <is the update mandatory>, - 'UserInput': <is user input required>, - 'EULAAccepted': <has the EULA been accepted>, - 'Severity': <update severity>, - 'NeedsReboot': <is the update installed and awaiting reboot>, - 'RebootBehavior': <will the update require a reboot>, - 'Categories': [ '<category 1>', - '<category 2>', - ...] - } - } + Dict of Updates: + {'<GUID>': { + 'Title': <title>, + 'KB': <KB>, + 'GUID': <the globally unique identifier for the update>, + 'Description': <description>, + 'Downloaded': <has the update been downloaded>, + 'Installed': <has the update been installed>, + 'Mandatory': <is the update mandatory>, + 'UserInput': <is user input required>, + 'EULAAccepted': <has the EULA been accepted>, + 'Severity': <update severity>, + 'NeedsReboot': <is the update installed and awaiting reboot>, + 'RebootBehavior': <will the update require a reboot>, + 'Categories': [ + '<category 1>', + '<category 2>', + ... ] + }} Summary of Updates: {'Total': <total number of updates returned>, 'Available': <updates that are not downloaded or installed>, 'Downloaded': <updates that are downloaded but not installed>, 'Installed': <updates installed (usually 0 unless installed=True)>, - 'Categories': { <category 1>: <total for that category>, - <category 2>: <total for category 2>, - ... } + 'Categories': { + <category 1>: <total for that category>, + <category 2>: <total for category 2>, + ... } } CLI Examples: @@ -517,6 +527,8 @@ def list( def installed(summary=False, kbs_only=False): """ + .. versionadded:: Sodium + Get a list of all updates that are currently installed on the system. .. note:: @@ -529,21 +541,25 @@ def installed(summary=False, kbs_only=False): the 1803 build and not those applied when the system was at the 1607 build. - .. versionadded:: Sodium - Args: - summary (bool): Return a summary instead of a detailed list of updates. - ``True`` will return a Summary, ``False`` will return a detailed - list of installed updates. Default is ``False`` - kbs_only (bool): Only return a list of KBs installed on the system. If - this parameter is passed, the ``summary`` parameter will be ignored. - Default is ``False`` + summary (bool): + Return a summary instead of a detailed list of updates. ``True`` + will return a Summary, ``False`` will return a detailed list of + installed updates. Default is ``False`` + + kbs_only (bool): + Only return a list of KBs installed on the system. If this parameter + is passed, the ``summary`` parameter will be ignored. Default is + ``False`` Returns: - dict: (``kbs_only=False``) Returns a dictionary of either a Summary or a - detailed list of updates installed on the system. - list: (``kbs_only=True``) Returns a list of KBs installed on the system. + dict: + Returns a dictionary of either a Summary or a detailed list of + updates installed on the system when ``kbs_only=False`` + + list: + Returns a list of KBs installed on the system when ``kbs_only=True`` CLI Examples: @@ -587,9 +603,9 @@ def download(names): combination of GUIDs, KB numbers, or names. GUIDs or KBs are preferred. - .. note:: - An error will be raised if there are more results than there are items - in the names parameter + .. note:: + An error will be raised if there are more results than there are + items in the names parameter Returns: @@ -750,7 +766,8 @@ def set_wu_settings( Number from 1 to 4 indicating the update level: 1. Never check for updates - 2. Check for updates but let me choose whether to download and install them + 2. Check for updates but let me choose whether to download and + install them 3. Download updates but let me choose whether to install them 4. Install updates automatically @@ -984,35 +1001,46 @@ def get_wu_settings(): Featured Updates: Boolean value that indicates whether to display notifications for featured updates. + Group Policy Required (Read-only): Boolean value that indicates whether Group Policy requires the Automatic Updates service. + Microsoft Update: Boolean value that indicates whether to turn on Microsoft Update for other Microsoft Products + Needs Reboot: Boolean value that indicates whether the machine is in a reboot pending state. + Non Admins Elevated: Boolean value that indicates whether non-administrators can perform some update-related actions without administrator approval. + Notification Level: + Number 1 to 4 indicating the update level: + 1. Never check for updates 2. Check for updates but let me choose whether to download and install them 3. Download updates but let me choose whether to install them 4. Install updates automatically + Read Only (Read-only): Boolean value that indicates whether the Automatic Update settings are read-only. + Recommended Updates: Boolean value that indicates whether to include optional or recommended updates when a search for updates and installation of updates is performed. + Scheduled Day: Days of the week on which Automatic Updates installs or uninstalls updates. + Scheduled Time: Time at which Automatic Updates installs or uninstalls updates. diff --git a/salt/utils/win_update.py b/salt/utils/win_update.py index b0c6117ffce6..8c6801577911 100644 --- a/salt/utils/win_update.py +++ b/salt/utils/win_update.py @@ -110,24 +110,25 @@ def list(self): .. code-block:: cfg - List of Updates: - {'<GUID>': {'Title': <title>, - 'KB': <KB>, - 'GUID': <the globally unique identifier for the update> - 'Description': <description>, - 'Downloaded': <has the update been downloaded>, - 'Installed': <has the update been installed>, - 'Mandatory': <is the update mandatory>, - 'UserInput': <is user input required>, - 'EULAAccepted': <has the EULA been accepted>, - 'Severity': <update severity>, - 'NeedsReboot': <is the update installed and awaiting reboot>, - 'RebootBehavior': <will the update require a reboot>, - 'Categories': [ '<category 1>', - '<category 2>', - ...] - } - } + Dict of Updates: + {'<GUID>': { + 'Title': <title>, + 'KB': <KB>, + 'GUID': <the globally unique identifier for the update>, + 'Description': <description>, + 'Downloaded': <has the update been downloaded>, + 'Installed': <has the update been installed>, + 'Mandatory': <is the update mandatory>, + 'UserInput': <is user input required>, + 'EULAAccepted': <has the EULA been accepted>, + 'Severity': <update severity>, + 'NeedsReboot': <is the update installed and awaiting reboot>, + 'RebootBehavior': <will the update require a reboot>, + 'Categories': [ + '<category 1>', + '<category 2>', + ... ] + }} Code Example: @@ -182,10 +183,12 @@ def summary(self): 'Available': <updates that are not downloaded or installed>, 'Downloaded': <updates that are downloaded but not installed>, 'Installed': <updates installed (usually 0 unless installed=True)>, - 'Categories': { <category 1>: <total for that category>, - <category 2>: <total for category 2>, - ... } + 'Categories': { + <category 1>: <total for that category>, + <category 2>: <total for category 2>, + ... } } + Code Example: .. code-block:: python @@ -291,9 +294,10 @@ def __init__(self, online=True): Args: - online (bool): Tells the Windows Update Agent go online to update - its local update database. ``True`` will go online. ``False`` - will use the local update database as is. Default is ``False`` + online (bool): + Tells the Windows Update Agent go online to update its local + update database. ``True`` will go online. ``False`` will use the + local update database as is. Default is ``False`` .. versionadded:: Sodium @@ -316,8 +320,12 @@ def updates(self): Updates class to expose the list and summary functions. Returns: - Updates: An instance of the Updates class with all updates for the - system. + + Updates: + An instance of the Updates class with all updates for the + system. + + Code Example: .. code-block:: python @@ -347,9 +355,10 @@ def refresh(self, online=True): Args: - online (bool): Tells the Windows Update Agent go online to update - its local update database. ``True`` will go online. ``False`` - will use the local update database as is. Default is ``False`` + online (bool): + Tells the Windows Update Agent go online to update its local + update database. ``True`` will go online. ``False`` will use the + local update database as is. Default is ``False`` .. versionadded:: Sodium @@ -431,23 +440,27 @@ def available( Args: - skip_hidden (bool): Skip hidden updates. Default is ``True`` - - skip_installed (bool): Skip installed updates. Default is ``True`` + skip_hidden (bool): + Skip hidden updates. Default is ``True`` - skip_mandatory (bool): Skip mandatory updates. Default is ``False`` + skip_installed (bool): + Skip installed updates. Default is ``True`` - skip_reboot (bool): Skip updates that can or do require reboot. - Default is ``False`` + skip_mandatory (bool): + Skip mandatory updates. Default is ``False`` - software (bool): Include software updates. Default is ``True`` + skip_reboot (bool): + Skip updates that can or do require reboot. Default is ``False`` - drivers (bool): Include driver updates. Default is ``True`` + software (bool): + Include software updates. Default is ``True`` - categories (list): Include updates that have these categories. - Default is none (all categories). + drivers (bool): + Include driver updates. Default is ``True`` - Categories include the following: + categories (list): + Include updates that have these categories. Default is none + (all categories). Categories include the following: * Critical Updates * Definition Updates @@ -463,10 +476,9 @@ def available( * Windows 8.1 and later drivers * Windows Defender - severities (list): Include updates that have these severities. - Default is none (all severities). - - Severities include the following: + severities (list): + Include updates that have these severities. Default is none + (all severities). Severities include the following: * Critical * Important @@ -545,11 +557,11 @@ def search(self, search_string): Args: - search_string (str, list): The search string to use to find the - update. This can be the GUID or KB of the update (preferred). It - can also be the full Title of the update or any part of the - Title. A partial Title search is less specific and can return - multiple results. + search_string (str, list): + The search string to use to find the update. This can be the + GUID or KB of the update (preferred). It can also be the full + Title of the update or any part of the Title. A partial Title + search is less specific and can return multiple results. Returns: Updates: An instance of Updates with the results of the search @@ -611,8 +623,9 @@ def download(self, updates): Args: - updates (Updates): An instance of the Updates class containing a the - updates to be downloaded. + updates (Updates): + An instance of the Updates class containing a the updates to be + downloaded. Returns: dict: A dictionary containing the results of the download @@ -724,8 +737,9 @@ def install(self, updates): Args: - updates (Updates): An instance of the Updates class containing a - the updates to be installed. + updates (Updates): + An instance of the Updates class containing a the updates to be + installed. Returns: dict: A dictionary containing the results of the installation @@ -842,8 +856,9 @@ def uninstall(self, updates): Args: - updates (Updates): An instance of the Updates class containing a - the updates to be uninstalled. + updates (Updates): + An instance of the Updates class containing a the updates to be + uninstalled. Returns: dict: A dictionary containing the results of the un-installation @@ -870,7 +885,7 @@ def uninstall(self, updates): return ret installer = self._session.CreateUpdateInstaller() - self._session.ClientApplicationID = "Salt: Install Update" + self._session.ClientApplicationID = "Salt: Uninstall Update" with salt.utils.winapi.Com(): uninstall_list = win32com.client.Dispatch("Microsoft.Update.UpdateColl") @@ -1029,7 +1044,8 @@ def _run(self, cmd): Internal function for running commands. Used by the uninstall function. Args: - cmd (str, list): The command to run + cmd (str, list): + The command to run Returns: str: The stdout of the command From b8fb1920258946c3aa8d55fb34cf6a93a1fb84e8 Mon Sep 17 00:00:00 2001 From: twangboy <slee@saltstack.com> Date: Tue, 14 Apr 2020 13:09:32 -0600 Subject: [PATCH 09/14] Fix some blacken issues --- salt/modules/win_wua.py | 2 +- salt/utils/win_update.py | 1 + tests/unit/modules/test_win_wua.py | 94 +++++++++++++---------------- tests/unit/utils/test_win_update.py | 64 ++++++++++++++------ 4 files changed, 89 insertions(+), 72 deletions(-) diff --git a/salt/modules/win_wua.py b/salt/modules/win_wua.py index eb1b9b593cd6..4036344cb132 100644 --- a/salt/modules/win_wua.py +++ b/salt/modules/win_wua.py @@ -583,7 +583,7 @@ def installed(summary=False, kbs_only=False): if kbs_only: list_kbs = set() for item in results: - list_kbs.update(results[item]['KBs']) + list_kbs.update(results[item]["KBs"]) return sorted(list_kbs) return updates.summary() if summary else results diff --git a/salt/utils/win_update.py b/salt/utils/win_update.py index 8c6801577911..bd470abdd812 100644 --- a/salt/utils/win_update.py +++ b/salt/utils/win_update.py @@ -255,6 +255,7 @@ class WindowsUpdateAgent(object): """ Class for working with the Windows update agent """ + # Error codes found at the following site: # https://msdn.microsoft.com/en-us/library/windows/desktop/hh968413(v=vs.85).aspx # https://technet.microsoft.com/en-us/library/cc720442(v=ws.10).aspx diff --git a/tests/unit/modules/test_win_wua.py b/tests/unit/modules/test_win_wua.py index 29bfdc127534..c52671fce191 100644 --- a/tests/unit/modules/test_win_wua.py +++ b/tests/unit/modules/test_win_wua.py @@ -15,40 +15,18 @@ from tests.support.unit import TestCase UPDATES_LIST = { - 'ca3bb521-a8ea-4e26-a563-2ad6e3108b9a': { - 'KBs': ['KB4481252'], - }, - '07609d43-d518-4e77-856e-d1b316d1b8a8': { - 'KBs': ['KB925673'], - }, - 'fbaa5360-a440-49d8-a3b6-0c4fc7ecaa19': { - 'KBs': ['KB4481252'], - }, - 'a873372b-7a5c-443c-8022-cd59a550bef4': { - 'KBs': ['KB3193497'], - }, - '14075cbe-822e-4004-963b-f50e08d45563': { - 'KBs': ['KB4540723'], - }, - 'd931e99c-4dda-4d39-9905-0f6a73f7195f': { - 'KBs': ['KB3193497'], - }, - 'afda9e11-44a0-4602-9e9b-423af11ecaed': { - 'KBs': ['KB4541329'], - }, - 'a0f997b1-1abe-4a46-941f-b37f732f9fbd': { - 'KBs': ['KB3193497'], - }, - 'eac02b09-d745-4891-b80f-400e0e5e4b6d': { - 'KBs': ['KB4052623'], - }, - '0689e74b-54d1-4f55-a916-96e3c737db90': { - 'KBs': ['KB890830'], - } -} -UPDATES_SUMMARY = { - 'Installed': 10, + "ca3bb521-a8ea-4e26-a563-2ad6e3108b9a": {"KBs": ["KB4481252"]}, + "07609d43-d518-4e77-856e-d1b316d1b8a8": {"KBs": ["KB925673"]}, + "fbaa5360-a440-49d8-a3b6-0c4fc7ecaa19": {"KBs": ["KB4481252"]}, + "a873372b-7a5c-443c-8022-cd59a550bef4": {"KBs": ["KB3193497"]}, + "14075cbe-822e-4004-963b-f50e08d45563": {"KBs": ["KB4540723"]}, + "d931e99c-4dda-4d39-9905-0f6a73f7195f": {"KBs": ["KB3193497"]}, + "afda9e11-44a0-4602-9e9b-423af11ecaed": {"KBs": ["KB4541329"]}, + "a0f997b1-1abe-4a46-941f-b37f732f9fbd": {"KBs": ["KB3193497"]}, + "eac02b09-d745-4891-b80f-400e0e5e4b6d": {"KBs": ["KB4052623"]}, + "0689e74b-54d1-4f55-a916-96e3c737db90": {"KBs": ["KB890830"]} } +UPDATES_SUMMARY = {"Installed": 10} class Updates(object): @@ -70,12 +48,16 @@ def test_installed(self): Test installed function default """ expected = UPDATES_LIST - with patch('salt.utils.winapi.Com', autospec=True), \ - patch('win32com.client.Dispatch', autospec=True), \ - patch.object(salt.utils.win_update.WindowsUpdateAgent, - 'refresh', autospec=True), \ - patch.object(salt.utils.win_update, 'Updates', autospec=True, - return_value=Updates()): + with patch( + "salt.utils.winapi.Com", autospec=True + ), patch( + "win32com.client.Dispatch", autospec=True + ), patch.object( + salt.utils.win_update.WindowsUpdateAgent, "refresh", autospec=True + ), patch.object( + salt.utils.win_update, "Updates", autospec=True, + return_value=Updates() + ): result = win_wua.installed() self.assertDictEqual(result, expected) @@ -85,12 +67,16 @@ def test_installed_summary(self): """ expected = UPDATES_SUMMARY # Remove all updates that are not installed - with patch('salt.utils.winapi.Com', autospec=True), \ - patch('win32com.client.Dispatch', autospec=True), \ - patch.object(salt.utils.win_update.WindowsUpdateAgent, - 'refresh', autospec=True), \ - patch.object(salt.utils.win_update, 'Updates', autospec=True, - return_value=Updates()): + with patch( + "salt.utils.winapi.Com", autospec=True + ), patch( + "win32com.client.Dispatch", autospec=True + ), patch.object( + salt.utils.win_update.WindowsUpdateAgent, "refresh", autospec=True + ), patch.object( + salt.utils.win_update, "Updates", autospec=True, + return_value=Updates() + ): result = win_wua.installed(summary=True) self.assertDictEqual(result, expected) @@ -100,14 +86,18 @@ def test_installed_kbs_only(self): """ expected = set() for update in UPDATES_LIST: - expected.update(UPDATES_LIST[update]['KBs']) + expected.update(UPDATES_LIST[update]["KBs"]) expected = sorted(expected) # Remove all updates that are not installed - with patch('salt.utils.winapi.Com', autospec=True), \ - patch('win32com.client.Dispatch', autospec=True), \ - patch.object(salt.utils.win_update.WindowsUpdateAgent, - 'refresh', autospec=True), \ - patch.object(salt.utils.win_update, 'Updates', autospec=True, - return_value=Updates()): + with patch( + "salt.utils.winapi.Com", autospec=True + ), patch( + "win32com.client.Dispatch", autospec=True + ), patch.object( + salt.utils.win_update.WindowsUpdateAgent, "refresh", autospec=True + ), patch.object( + salt.utils.win_update, "Updates", autospec=True, + return_value=Updates() + ): result = win_wua.installed(kbs_only=True) self.assertListEqual(result, expected) diff --git a/tests/unit/utils/test_win_update.py b/tests/unit/utils/test_win_update.py index bb24268ccf8f..da4757aed0e3 100644 --- a/tests/unit/utils/test_win_update.py +++ b/tests/unit/utils/test_win_update.py @@ -3,24 +3,29 @@ # Import Python Libs from __future__ import absolute_import, unicode_literals, print_function +# Import Salt Libs +import salt.utils.win_update as win_update + # Import Salt Testing Libs from tests.support.mock import patch, MagicMock from tests.support.unit import TestCase -# Import Salt Libs -import salt.utils.win_update as win_update - -# @skipIf(not salt.utils.platform.is_windows(), 'Only test on Windows systems') class WinUpdateTestCase(TestCase): - ''' + """ Test cases for salt.utils.win_update - ''' + """ def test_installed_no_updates(self): - with patch('salt.utils.winapi.Com', autospec=True), \ - patch('win32com.client.Dispatch', autospec=True), \ - patch.object(win_update.WindowsUpdateAgent, 'refresh', autospec=True): - + """ + Test installed when there are no updates on the system + """ + with patch( + "salt.utils.winapi.Com", autospec=True + ), patch( + "win32com.client.Dispatch", autospec=True + ), patch.object( + win_update.WindowsUpdateAgent, "refresh", autospec=True + ): wua = win_update.WindowsUpdateAgent(online=False) wua._updates = [] @@ -29,9 +34,16 @@ def test_installed_no_updates(self): assert installed_updates.updates.Add.call_count == 0 def test_installed_no_updates_installed(self): - with patch('salt.utils.winapi.Com', autospec=True), \ - patch('win32com.client.Dispatch', autospec=True), \ - patch.object(win_update.WindowsUpdateAgent, 'refresh', autospec=True): + """ + Test installed when there are no Installed updates on the system + """ + with patch( + "salt.utils.winapi.Com", autospec=True + ), patch( + "win32com.client.Dispatch", autospec=True + ), patch.object( + win_update.WindowsUpdateAgent, "refresh", autospec=True + ): wua = win_update.WindowsUpdateAgent(online=False) wua._updates = [ @@ -45,9 +57,16 @@ def test_installed_no_updates_installed(self): assert installed_updates.updates.Add.call_count == 0 def test_installed_updates_all_installed(self): - with patch('salt.utils.winapi.Com', autospec=True), \ - patch('win32com.client.Dispatch', autospec=True), \ - patch.object(win_update.WindowsUpdateAgent, 'refresh', autospec=True): + """ + Test installed when all updates on the system are Installed + """ + with patch( + "salt.utils.winapi.Com", autospec=True + ), patch( + "win32com.client.Dispatch", autospec=True + ), patch.object( + win_update.WindowsUpdateAgent, "refresh", autospec=True + ): wua = win_update.WindowsUpdateAgent(online=False) wua._updates = [ @@ -61,9 +80,16 @@ def test_installed_updates_all_installed(self): assert installed_updates.updates.Add.call_count == 3 def test_installed_updates_some_installed(self): - with patch('salt.utils.winapi.Com', autospec=True), \ - patch('win32com.client.Dispatch', autospec=True), \ - patch.object(win_update.WindowsUpdateAgent, 'refresh', autospec=True): + """ + Test installed when some updates are installed on the system + """ + with patch( + "salt.utils.winapi.Com", autospec=True + ), patch( + "win32com.client.Dispatch", autospec=True + ), patch.object( + win_update.WindowsUpdateAgent, "refresh", autospec=True + ): wua = win_update.WindowsUpdateAgent(online=False) wua._updates = [ From de70b6a0832e5e0c48776d5904aa90529f3ff8fe Mon Sep 17 00:00:00 2001 From: twangboy <slee@saltstack.com> Date: Tue, 14 Apr 2020 14:17:00 -0600 Subject: [PATCH 10/14] Fix some docs, blackened, and address some docs issues --- salt/modules/win_wua.py | 17 +++++++++------ salt/utils/win_update.py | 8 ++++--- tests/unit/modules/test_win_wua.py | 23 ++++++-------------- tests/unit/utils/test_win_update.py | 33 +++++++++-------------------- 4 files changed, 32 insertions(+), 49 deletions(-) diff --git a/salt/modules/win_wua.py b/salt/modules/win_wua.py index 4036344cb132..e6c4ab681c03 100644 --- a/salt/modules/win_wua.py +++ b/salt/modules/win_wua.py @@ -173,7 +173,7 @@ def available( online (bool): Tells the Windows Update Agent go online to update its local update database. ``True`` will go online. ``False`` will use the local - update database as is. Default is ``False`` + update database as is. Default is ``True`` .. versionadded:: Sodium @@ -283,7 +283,7 @@ def get(name, download=False, install=False, online=True): online (bool): Tells the Windows Update Agent go online to update its local update database. ``True`` will go online. ``False`` will use the local - update database as is. Default is ``False`` + update database as is. Default is ``True`` .. versionadded:: Sodium @@ -434,7 +434,7 @@ def list( online (bool): Tells the Windows Update Agent go online to update its local update database. ``True`` will go online. ``False`` will use the local - update database as is. Default is ``False`` + update database as is. Default is ``True`` .. versionadded:: Sodium @@ -532,6 +532,7 @@ def installed(summary=False, kbs_only=False): Get a list of all updates that are currently installed on the system. .. note:: + This list may not necessarily match the Update History on the machine. This will only show the updates that apply to the current build of Windows. So, for example, the system may have shipped with Windows 10 @@ -603,15 +604,16 @@ def download(names): combination of GUIDs, KB numbers, or names. GUIDs or KBs are preferred. - .. note:: - An error will be raised if there are more results than there are - items in the names parameter + .. note:: + + An error will be raised if there are more results than there are + items in the names parameter Returns: dict: A dictionary containing the details about the downloaded updates - CLI Examples: + CLI Example: .. code-block:: bash @@ -657,6 +659,7 @@ def install(names): preferred. .. note:: + An error will be raised if there are more results than there are items in the names parameter diff --git a/salt/utils/win_update.py b/salt/utils/win_update.py index bd470abdd812..6ff7225c0129 100644 --- a/salt/utils/win_update.py +++ b/salt/utils/win_update.py @@ -43,7 +43,8 @@ class Updates(object): Wrapper around the 'Microsoft.Update.UpdateColl' instance Adds the list and summary functions. For use by the WindowUpdateAgent class. - Usage: + Code Example: + .. code-block:: python # Create an instance @@ -298,7 +299,7 @@ def __init__(self, online=True): online (bool): Tells the Windows Update Agent go online to update its local update database. ``True`` will go online. ``False`` will use the - local update database as is. Default is ``False`` + local update database as is. Default is ``True`` .. versionadded:: Sodium @@ -359,7 +360,7 @@ def refresh(self, online=True): online (bool): Tells the Windows Update Agent go online to update its local update database. ``True`` will go online. ``False`` will use the - local update database as is. Default is ``False`` + local update database as is. Default is ``True`` .. versionadded:: Sodium @@ -848,6 +849,7 @@ def uninstall(self, updates): collection using the ``search`` or ``available`` functions. .. note:: + Starting with Windows 10 the Windows Update Agent is unable to uninstall updates. An ``Uninstall Not Allowed`` error is returned. If this error is encountered this function will instead attempt to diff --git a/tests/unit/modules/test_win_wua.py b/tests/unit/modules/test_win_wua.py index c52671fce191..5e811e586b9c 100644 --- a/tests/unit/modules/test_win_wua.py +++ b/tests/unit/modules/test_win_wua.py @@ -24,7 +24,7 @@ "afda9e11-44a0-4602-9e9b-423af11ecaed": {"KBs": ["KB4541329"]}, "a0f997b1-1abe-4a46-941f-b37f732f9fbd": {"KBs": ["KB3193497"]}, "eac02b09-d745-4891-b80f-400e0e5e4b6d": {"KBs": ["KB4052623"]}, - "0689e74b-54d1-4f55-a916-96e3c737db90": {"KBs": ["KB890830"]} + "0689e74b-54d1-4f55-a916-96e3c737db90": {"KBs": ["KB890830"]}, } UPDATES_SUMMARY = {"Installed": 10} @@ -48,15 +48,12 @@ def test_installed(self): Test installed function default """ expected = UPDATES_LIST - with patch( - "salt.utils.winapi.Com", autospec=True - ), patch( + with patch("salt.utils.winapi.Com", autospec=True), patch( "win32com.client.Dispatch", autospec=True ), patch.object( salt.utils.win_update.WindowsUpdateAgent, "refresh", autospec=True ), patch.object( - salt.utils.win_update, "Updates", autospec=True, - return_value=Updates() + salt.utils.win_update, "Updates", autospec=True, return_value=Updates() ): result = win_wua.installed() self.assertDictEqual(result, expected) @@ -67,15 +64,12 @@ def test_installed_summary(self): """ expected = UPDATES_SUMMARY # Remove all updates that are not installed - with patch( - "salt.utils.winapi.Com", autospec=True - ), patch( + with patch("salt.utils.winapi.Com", autospec=True), patch( "win32com.client.Dispatch", autospec=True ), patch.object( salt.utils.win_update.WindowsUpdateAgent, "refresh", autospec=True ), patch.object( - salt.utils.win_update, "Updates", autospec=True, - return_value=Updates() + salt.utils.win_update, "Updates", autospec=True, return_value=Updates() ): result = win_wua.installed(summary=True) self.assertDictEqual(result, expected) @@ -89,15 +83,12 @@ def test_installed_kbs_only(self): expected.update(UPDATES_LIST[update]["KBs"]) expected = sorted(expected) # Remove all updates that are not installed - with patch( - "salt.utils.winapi.Com", autospec=True - ), patch( + with patch("salt.utils.winapi.Com", autospec=True), patch( "win32com.client.Dispatch", autospec=True ), patch.object( salt.utils.win_update.WindowsUpdateAgent, "refresh", autospec=True ), patch.object( - salt.utils.win_update, "Updates", autospec=True, - return_value=Updates() + salt.utils.win_update, "Updates", autospec=True, return_value=Updates() ): result = win_wua.installed(kbs_only=True) self.assertListEqual(result, expected) diff --git a/tests/unit/utils/test_win_update.py b/tests/unit/utils/test_win_update.py index da4757aed0e3..71cbf88ee108 100644 --- a/tests/unit/utils/test_win_update.py +++ b/tests/unit/utils/test_win_update.py @@ -1,13 +1,13 @@ # -*- coding: utf-8 -*- # Import Python Libs -from __future__ import absolute_import, unicode_literals, print_function +from __future__ import absolute_import, print_function, unicode_literals # Import Salt Libs import salt.utils.win_update as win_update # Import Salt Testing Libs -from tests.support.mock import patch, MagicMock +from tests.support.mock import MagicMock, patch from tests.support.unit import TestCase @@ -15,13 +15,12 @@ class WinUpdateTestCase(TestCase): """ Test cases for salt.utils.win_update """ + def test_installed_no_updates(self): """ Test installed when there are no updates on the system """ - with patch( - "salt.utils.winapi.Com", autospec=True - ), patch( + with patch("salt.utils.winapi.Com", autospec=True), patch( "win32com.client.Dispatch", autospec=True ), patch.object( win_update.WindowsUpdateAgent, "refresh", autospec=True @@ -37,13 +36,9 @@ def test_installed_no_updates_installed(self): """ Test installed when there are no Installed updates on the system """ - with patch( - "salt.utils.winapi.Com", autospec=True - ), patch( + with patch("salt.utils.winapi.Com", autospec=True), patch( "win32com.client.Dispatch", autospec=True - ), patch.object( - win_update.WindowsUpdateAgent, "refresh", autospec=True - ): + ), patch.object(win_update.WindowsUpdateAgent, "refresh", autospec=True): wua = win_update.WindowsUpdateAgent(online=False) wua._updates = [ @@ -60,13 +55,9 @@ def test_installed_updates_all_installed(self): """ Test installed when all updates on the system are Installed """ - with patch( - "salt.utils.winapi.Com", autospec=True - ), patch( + with patch("salt.utils.winapi.Com", autospec=True), patch( "win32com.client.Dispatch", autospec=True - ), patch.object( - win_update.WindowsUpdateAgent, "refresh", autospec=True - ): + ), patch.object(win_update.WindowsUpdateAgent, "refresh", autospec=True): wua = win_update.WindowsUpdateAgent(online=False) wua._updates = [ @@ -83,13 +74,9 @@ def test_installed_updates_some_installed(self): """ Test installed when some updates are installed on the system """ - with patch( - "salt.utils.winapi.Com", autospec=True - ), patch( + with patch("salt.utils.winapi.Com", autospec=True), patch( "win32com.client.Dispatch", autospec=True - ), patch.object( - win_update.WindowsUpdateAgent, "refresh", autospec=True - ): + ), patch.object(win_update.WindowsUpdateAgent, "refresh", autospec=True): wua = win_update.WindowsUpdateAgent(online=False) wua._updates = [ From ea6de1b2f61c874e09f6352e0e12307a05870ecc Mon Sep 17 00:00:00 2001 From: twangboy <slee@saltstack.com> Date: Tue, 14 Apr 2020 14:36:13 -0600 Subject: [PATCH 11/14] Fix some black --- tests/unit/modules/test_win_wua.py | 1 + tests/unit/utils/test_win_update.py | 4 +--- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/unit/modules/test_win_wua.py b/tests/unit/modules/test_win_wua.py index 5e811e586b9c..f7e8c405e6a9 100644 --- a/tests/unit/modules/test_win_wua.py +++ b/tests/unit/modules/test_win_wua.py @@ -43,6 +43,7 @@ class WinWuaInstalledTestCase(TestCase): """ Test the functions in the win_wua.installed function """ + def test_installed(self): """ Test installed function default diff --git a/tests/unit/utils/test_win_update.py b/tests/unit/utils/test_win_update.py index 71cbf88ee108..8b15592ee730 100644 --- a/tests/unit/utils/test_win_update.py +++ b/tests/unit/utils/test_win_update.py @@ -22,9 +22,7 @@ def test_installed_no_updates(self): """ with patch("salt.utils.winapi.Com", autospec=True), patch( "win32com.client.Dispatch", autospec=True - ), patch.object( - win_update.WindowsUpdateAgent, "refresh", autospec=True - ): + ), patch.object(win_update.WindowsUpdateAgent, "refresh", autospec=True): wua = win_update.WindowsUpdateAgent(online=False) wua._updates = [] From 23dfd6146bebb1c5298a89fbfc8390aa62e2de03 Mon Sep 17 00:00:00 2001 From: twangboy <slee@saltstack.com> Date: Fri, 17 Apr 2020 10:40:05 -0600 Subject: [PATCH 12/14] Skip tests on non-Windows systems --- tests/unit/modules/test_win_wua.py | 3 ++- tests/unit/utils/test_win_update.py | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/unit/modules/test_win_wua.py b/tests/unit/modules/test_win_wua.py index f7e8c405e6a9..18492421e736 100644 --- a/tests/unit/modules/test_win_wua.py +++ b/tests/unit/modules/test_win_wua.py @@ -12,7 +12,7 @@ # Import Salt Testing Libs from tests.support.mock import patch -from tests.support.unit import TestCase +from tests.support.unit import TestCase, skipIf UPDATES_LIST = { "ca3bb521-a8ea-4e26-a563-2ad6e3108b9a": {"KBs": ["KB4481252"]}, @@ -39,6 +39,7 @@ def summary(): return UPDATES_SUMMARY +@skipIf(not salt.utils.platform.is_windows(), "System is not Windows") class WinWuaInstalledTestCase(TestCase): """ Test the functions in the win_wua.installed function diff --git a/tests/unit/utils/test_win_update.py b/tests/unit/utils/test_win_update.py index 8b15592ee730..6faf81f14304 100644 --- a/tests/unit/utils/test_win_update.py +++ b/tests/unit/utils/test_win_update.py @@ -5,12 +5,14 @@ # Import Salt Libs import salt.utils.win_update as win_update +import salt.utils.platform # Import Salt Testing Libs from tests.support.mock import MagicMock, patch -from tests.support.unit import TestCase +from tests.support.unit import TestCase, skipIf +@skipIf(not salt.utils.platform.is_windows(), "System is not Windows") class WinUpdateTestCase(TestCase): """ Test cases for salt.utils.win_update From 010627f7079f49d780b9c92ff036555f8a6656ab Mon Sep 17 00:00:00 2001 From: twangboy <slee@saltstack.com> Date: Fri, 17 Apr 2020 11:53:07 -0600 Subject: [PATCH 13/14] Fix some black stuff --- tests/unit/utils/test_win_update.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/utils/test_win_update.py b/tests/unit/utils/test_win_update.py index 6faf81f14304..8a8f0e712e79 100644 --- a/tests/unit/utils/test_win_update.py +++ b/tests/unit/utils/test_win_update.py @@ -4,12 +4,12 @@ from __future__ import absolute_import, print_function, unicode_literals # Import Salt Libs -import salt.utils.win_update as win_update import salt.utils.platform +import salt.utils.win_update as win_update # Import Salt Testing Libs from tests.support.mock import MagicMock, patch -from tests.support.unit import TestCase, skipIf +from tests.support.unit import skipIf, TestCase @skipIf(not salt.utils.platform.is_windows(), "System is not Windows") From cd07707cecaf4beb57a20b76f7a0923b73de9f78 Mon Sep 17 00:00:00 2001 From: twangboy <slee@saltstack.com> Date: Fri, 17 Apr 2020 12:05:02 -0600 Subject: [PATCH 14/14] More black --- tests/unit/utils/test_win_update.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/unit/utils/test_win_update.py b/tests/unit/utils/test_win_update.py index 8a8f0e712e79..c660ccd03c58 100644 --- a/tests/unit/utils/test_win_update.py +++ b/tests/unit/utils/test_win_update.py @@ -9,7 +9,7 @@ # Import Salt Testing Libs from tests.support.mock import MagicMock, patch -from tests.support.unit import skipIf, TestCase +from tests.support.unit import TestCase, skipIf @skipIf(not salt.utils.platform.is_windows(), "System is not Windows")