mirror of
https://github.com/saltstack/salt.git
synced 2025-04-16 09:40:20 +00:00
Add the ability to remove a KB from the DISM module
Adds the ability to remove a KB using the DISM module by passing just the KB number Adds a state for doing the same thing
This commit is contained in:
parent
29e00d7f11
commit
38e23f1b44
5 changed files with 325 additions and 7 deletions
1
changelog/62366.added
Normal file
1
changelog/62366.added
Normal file
|
@ -0,0 +1 @@
|
|||
Added the ability to remove a KB using the DISM state/execution modules
|
|
@ -11,6 +11,7 @@ import re
|
|||
|
||||
import salt.utils.platform
|
||||
import salt.utils.versions
|
||||
from salt.exceptions import CommandExecutionError
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
__virtualname__ = "dism"
|
||||
|
@ -517,12 +518,13 @@ def remove_package(package, image=None, restart=False):
|
|||
Args:
|
||||
package (str): The full path to the package. Can be either a .cab file
|
||||
or a folder. Should point to the original source of the package, not
|
||||
to where the file is installed. This can also be the name of a package as listed in
|
||||
``dism.installed_packages``
|
||||
to where the file is installed. This can also be the name of a
|
||||
package as listed in ``dism.installed_packages``
|
||||
image (Optional[str]): The path to the root directory of an offline
|
||||
Windows image. If `None` is passed, the running operating system is
|
||||
targeted. Default is None.
|
||||
restart (Optional[bool]): Reboot the machine if required by the install
|
||||
restart (Optional[bool]): Reboot the machine if required by the
|
||||
uninstall
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing the results of the command
|
||||
|
@ -555,6 +557,73 @@ def remove_package(package, image=None, restart=False):
|
|||
return __salt__["cmd.run_all"](cmd)
|
||||
|
||||
|
||||
def get_kg_package_name(kb, image=None):
|
||||
"""
|
||||
Get the actual package name on the system based on the KB name
|
||||
|
||||
Args:
|
||||
kb (str): The name of the KB to remove. Can also be just the KB number
|
||||
image (Optional[str]): The path to the root directory of an offline
|
||||
Windows image. If `None` is passed, the running operating system is
|
||||
targeted. Default is None.
|
||||
|
||||
Returns:
|
||||
str: The name of the package found on the system
|
||||
None: If the package is not installed on the system
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# Get the package name for KB1231231
|
||||
salt '*' dism.get_kb_package_name KB1231231
|
||||
|
||||
# Get the package name for KB1231231 using just the number
|
||||
salt '*' dism.get_kb_package_name 1231231
|
||||
"""
|
||||
packages = installed_packages(image=image)
|
||||
search = kb.upper() if kb.lower().startswith("kb") else "KB{}".format(kb)
|
||||
for package in packages:
|
||||
if "_{}~".format(search) in package:
|
||||
return package
|
||||
return None
|
||||
|
||||
|
||||
def remove_kb(kb, image=None, restart=False):
|
||||
"""
|
||||
Remove a package by passing a KB number. This searches the installed
|
||||
packages to get the full package name of the KB. It then calls the
|
||||
``dism.remove_package`` function to remove the package.
|
||||
|
||||
Args:
|
||||
kb (str): The name of the KB to remove. Can also be just the KB number
|
||||
image (Optional[str]): The path to the root directory of an offline
|
||||
Windows image. If `None` is passed, the running operating system is
|
||||
targeted. Default is None.
|
||||
restart (Optional[bool]): Reboot the machine if required by the
|
||||
uninstall
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing the results of the command
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# Remove the KB5007575 just passing the number
|
||||
salt '*' dism.remove_kb 5007575
|
||||
|
||||
# Remove the KB5007575 just passing the full name
|
||||
salt '*' dism.remove_kb KB5007575
|
||||
"""
|
||||
pkg_name = get_kb_package_name(kb=kb, image=image)
|
||||
if pkg_name is None:
|
||||
msg = "{} not installed".format(search)
|
||||
raise CommandExecutionError(msg)
|
||||
log.debug("Found: %s", pkg_name)
|
||||
return remove_package(package=pkg_name, image=image, restart=restart)
|
||||
|
||||
|
||||
def installed_packages(image=None):
|
||||
"""
|
||||
List the packages installed on the system
|
||||
|
@ -573,7 +642,12 @@ def installed_packages(image=None):
|
|||
|
||||
salt '*' dism.installed_packages
|
||||
"""
|
||||
return _get_components("Package Identity", "Packages", "Installed")
|
||||
return _get_components(
|
||||
type_regex="Package Identity",
|
||||
plural_type="Packages",
|
||||
install_value="Installed",
|
||||
image=image,
|
||||
)
|
||||
|
||||
|
||||
def package_info(package, image=None):
|
||||
|
|
|
@ -99,7 +99,8 @@ def capability_removed(name, image=None, restart=False):
|
|||
image (Optional[str]): The path to the root directory of an offline
|
||||
Windows image. If `None` is passed, the running operating system is
|
||||
targeted. Default is None.
|
||||
restart (Optional[bool]): Reboot the machine if required by the install
|
||||
restart (Optional[bool]): Reboot the machine if required by the
|
||||
uninstall
|
||||
|
||||
Example:
|
||||
Run ``dism.installed_capabilities`` to get a list of installed
|
||||
|
@ -223,7 +224,8 @@ def feature_removed(name, remove_payload=False, image=None, restart=False):
|
|||
image (Optional[str]): The path to the root directory of an offline
|
||||
Windows image. If `None` is passed, the running operating system is
|
||||
targeted. Default is None.
|
||||
restart (Optional[bool]): Reboot the machine if required by the install
|
||||
restart (Optional[bool]): Reboot the machine if required by the
|
||||
uninstall
|
||||
|
||||
Example:
|
||||
Run ``dism.installed_features`` to get a list of installed features.
|
||||
|
@ -352,7 +354,8 @@ def package_removed(name, image=None, restart=False):
|
|||
image (Optional[str]): The path to the root directory of an offline
|
||||
Windows image. If `None` is passed, the running operating system is
|
||||
targeted. Default is None.
|
||||
restart (Optional[bool]): Reboot the machine if required by the install
|
||||
restart (Optional[bool]): Reboot the machine if required by the
|
||||
uninstall
|
||||
|
||||
Example:
|
||||
|
||||
|
@ -414,3 +417,66 @@ def package_removed(name, image=None, restart=False):
|
|||
ret["changes"]["package"] = changes
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def kb_removed(name, image=None, restart=False):
|
||||
"""
|
||||
Uninstall a KB package
|
||||
|
||||
Args:
|
||||
name (str): The name of the KB. Can be with or without the KB at the
|
||||
beginning.
|
||||
image (Optional[str]): The path to the root directory of an offline
|
||||
Windows image. If `None` is passed, the running operating system is
|
||||
targeted. Default is None.
|
||||
restart (Optional[bool]): Reboot the machine if required by the
|
||||
uninstall
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
# Example using full KB name
|
||||
remove_KB1231231:
|
||||
dism.package_installed:
|
||||
- name: KB1231231
|
||||
|
||||
# Example using just he KB number
|
||||
remove_KB1231231:
|
||||
dism.package_installed:
|
||||
- name: 1231231
|
||||
"""
|
||||
ret = {"name": name, "result": True, "comment": "", "changes": {}}
|
||||
|
||||
pkg_name = __salt__["dism.get_kb_package_name"](kb=name, image=image)
|
||||
|
||||
# If pkg_name is None, the package is not installed
|
||||
if pkg_name is None:
|
||||
ret["comment"] = "{} is not installed".format(name)
|
||||
return ret
|
||||
|
||||
if __opts__["test"]:
|
||||
ret["changes"]["package"] = "{} will be removed".format(name)
|
||||
ret["result"] = None
|
||||
return ret
|
||||
|
||||
# Fail if using a non-existent package path
|
||||
old = __salt__["dism.installed_packages"]()
|
||||
|
||||
# Remove the package
|
||||
status = __salt__["dism.remove_kb"](kb=name, image=image, restart=restart)
|
||||
|
||||
if status["retcode"] not in [0, 1641, 3010]:
|
||||
ret["comment"] = "Failed to remove {}: {}".format(name, status["stdout"])
|
||||
ret["result"] = False
|
||||
return ret
|
||||
|
||||
new = __salt__["dism.installed_packages"]()
|
||||
changes = salt.utils.data.compare_lists(old, new)
|
||||
|
||||
if changes:
|
||||
ret["comment"] = "Removed {}".format(name)
|
||||
ret["changes"] = status
|
||||
ret["changes"]["package"] = changes
|
||||
|
||||
return ret
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import pytest
|
||||
|
||||
import salt.modules.win_dism as dism
|
||||
from salt.exceptions import CommandExecutionError
|
||||
from tests.support.mock import MagicMock, patch
|
||||
|
||||
|
||||
|
@ -322,6 +323,49 @@ def test_remove_package():
|
|||
)
|
||||
|
||||
|
||||
def test_remove_kb():
|
||||
"""
|
||||
Test uninstalling a KB with DISM
|
||||
"""
|
||||
pkg_name = "Package_for_KB1002345~31bf3856ad364e35~amd64~~22000.345.1.1"
|
||||
mock_search = MagicMock(return_value=[pkg_name])
|
||||
mock_remove = MagicMock()
|
||||
with patch("salt.modules.win_dism.installed_packages", mock_search):
|
||||
with patch("salt.modules.win_dism.remove_package", mock_remove):
|
||||
dism.remove_kb("KB1002345")
|
||||
mock_remove.assert_called_once_with(
|
||||
package=pkg_name,
|
||||
image=None,
|
||||
restart=False,
|
||||
)
|
||||
|
||||
|
||||
def test_remove_kb_number():
|
||||
"""
|
||||
Test uninstalling a KB with DISM with just the KB number
|
||||
"""
|
||||
pkg_name = "Package_for_KB1002345~31bf3856ad364e35~amd64~~22000.345.1.1"
|
||||
mock_search = MagicMock(return_value=[pkg_name])
|
||||
mock_remove = MagicMock()
|
||||
with patch("salt.modules.win_dism.installed_packages", mock_search):
|
||||
with patch("salt.modules.win_dism.remove_package", mock_remove):
|
||||
dism.remove_kb("1002345")
|
||||
mock_remove.assert_called_once_with(
|
||||
package=pkg_name,
|
||||
image=None,
|
||||
restart=False,
|
||||
)
|
||||
|
||||
|
||||
def test_remove_kb_not_found():
|
||||
pkg_name = "Package_for_KB1002345~31bf3856ad364e35~amd64~~22000.345.1.1"
|
||||
mock_search = MagicMock(return_value=[pkg_name])
|
||||
with patch("salt.modules.win_dism.installed_packages", mock_search):
|
||||
with pytest.raises(CommandExecutionError) as err:
|
||||
dism.remove_kb("1001111")
|
||||
assert str(err.value) == "KB1001111 not installed"
|
||||
|
||||
|
||||
def test_installed_packages():
|
||||
"""
|
||||
Test getting all the installed features
|
||||
|
|
|
@ -548,3 +548,136 @@ def test_package_removed_removed():
|
|||
mock_removed.assert_called_once_with()
|
||||
assert not mock_remove.called
|
||||
assert out == expected
|
||||
|
||||
|
||||
def test_kb_removed():
|
||||
"""
|
||||
Test removing a package using the KB number
|
||||
"""
|
||||
pkg_name = "Package_for_KB1231231~31bf3856ad364e35~amd64~~22000.345.1.1"
|
||||
expected = {
|
||||
"comment": "Removed KB1231231",
|
||||
"changes": {"package": {"old": [pkg_name]}, "retcode": 0},
|
||||
"name": "KB1231231",
|
||||
"result": True,
|
||||
}
|
||||
pre_removed = [
|
||||
"Package_for_KB5007575~31bf3856ad364e35~amd64~~22000.345.1.1",
|
||||
"Package_for_KB5012170~31bf3856ad364e35~amd64~~22000.850.1.1",
|
||||
pkg_name,
|
||||
]
|
||||
post_removed = [
|
||||
"Package_for_KB5007575~31bf3856ad364e35~amd64~~22000.345.1.1",
|
||||
"Package_for_KB5012170~31bf3856ad364e35~amd64~~22000.850.1.1",
|
||||
]
|
||||
mock_get_name = MagicMock(return_value=pkg_name)
|
||||
mock_installed = MagicMock(side_effect=[pre_removed, post_removed])
|
||||
mock_remove = MagicMock(return_value={"retcode": 0})
|
||||
|
||||
patch_salt = {
|
||||
"dism.get_kb_package_name": mock_get_name,
|
||||
"dism.installed_packages": mock_installed,
|
||||
"dism.remove_kb": mock_remove,
|
||||
}
|
||||
|
||||
with patch.dict(dism.__salt__, patch_salt):
|
||||
with patch.dict(dism.__opts__, {"test": False}):
|
||||
result = dism.kb_removed("KB1231231")
|
||||
mock_remove.assert_called_once_with(
|
||||
kb="KB1231231", image=None, restart=False
|
||||
)
|
||||
assert result == expected
|
||||
|
||||
|
||||
def test_kb_removed_not_installed():
|
||||
"""
|
||||
Test removing a package using the KB number when the package is not
|
||||
installed
|
||||
"""
|
||||
pkg_name = None
|
||||
expected = {
|
||||
"comment": "KB1231231 is not installed",
|
||||
"changes": {},
|
||||
"name": "KB1231231",
|
||||
"result": True,
|
||||
}
|
||||
mock_get_name = MagicMock(return_value=pkg_name)
|
||||
|
||||
patch_salt = {"dism.get_kb_package_name": mock_get_name}
|
||||
|
||||
with patch.dict(dism.__salt__, patch_salt):
|
||||
result = dism.kb_removed("KB1231231")
|
||||
assert result == expected
|
||||
|
||||
|
||||
def test_kb_removed_test():
|
||||
"""
|
||||
Test removing a package using the KB number with test=True
|
||||
"""
|
||||
pkg_name = "Package_for_KB1231231~31bf3856ad364e35~amd64~~22000.345.1.1"
|
||||
expected = {
|
||||
"comment": "",
|
||||
"changes": {"package": "KB1231231 will be removed"},
|
||||
"name": "KB1231231",
|
||||
"result": None,
|
||||
}
|
||||
pre_removed = [
|
||||
"Package_for_KB5007575~31bf3856ad364e35~amd64~~22000.345.1.1",
|
||||
"Package_for_KB5012170~31bf3856ad364e35~amd64~~22000.850.1.1",
|
||||
pkg_name,
|
||||
]
|
||||
post_removed = [
|
||||
"Package_for_KB5007575~31bf3856ad364e35~amd64~~22000.345.1.1",
|
||||
"Package_for_KB5012170~31bf3856ad364e35~amd64~~22000.850.1.1",
|
||||
]
|
||||
mock_get_name = MagicMock(return_value=pkg_name)
|
||||
mock_installed = MagicMock(side_effect=[pre_removed, post_removed])
|
||||
|
||||
patch_salt = {
|
||||
"dism.get_kb_package_name": mock_get_name,
|
||||
"dism.installed_packages": mock_installed,
|
||||
}
|
||||
|
||||
with patch.dict(dism.__salt__, patch_salt):
|
||||
with patch.dict(dism.__opts__, {"test": True}):
|
||||
result = dism.kb_removed("KB1231231")
|
||||
assert result == expected
|
||||
|
||||
|
||||
def test_kb_removed_failed():
|
||||
"""
|
||||
Test removing a package using the KB number with a failure
|
||||
"""
|
||||
pkg_name = "Package_for_KB1231231~31bf3856ad364e35~amd64~~22000.345.1.1"
|
||||
expected = {
|
||||
"comment": "Failed to remove KB1231231: error",
|
||||
"changes": {},
|
||||
"name": "KB1231231",
|
||||
"result": False,
|
||||
}
|
||||
pre_removed = [
|
||||
"Package_for_KB5007575~31bf3856ad364e35~amd64~~22000.345.1.1",
|
||||
"Package_for_KB5012170~31bf3856ad364e35~amd64~~22000.850.1.1",
|
||||
pkg_name,
|
||||
]
|
||||
post_removed = [
|
||||
"Package_for_KB5007575~31bf3856ad364e35~amd64~~22000.345.1.1",
|
||||
"Package_for_KB5012170~31bf3856ad364e35~amd64~~22000.850.1.1",
|
||||
]
|
||||
mock_get_name = MagicMock(return_value=pkg_name)
|
||||
mock_installed = MagicMock(side_effect=[pre_removed, post_removed])
|
||||
mock_remove = MagicMock(return_value={"retcode": 1, "stdout": "error"})
|
||||
|
||||
patch_salt = {
|
||||
"dism.get_kb_package_name": mock_get_name,
|
||||
"dism.installed_packages": mock_installed,
|
||||
"dism.remove_kb": mock_remove,
|
||||
}
|
||||
|
||||
with patch.dict(dism.__salt__, patch_salt):
|
||||
with patch.dict(dism.__opts__, {"test": False}):
|
||||
result = dism.kb_removed("KB1231231")
|
||||
mock_remove.assert_called_once_with(
|
||||
kb="KB1231231", image=None, restart=False
|
||||
)
|
||||
assert result == expected
|
||||
|
|
Loading…
Add table
Reference in a new issue