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.platform
|
||||||
import salt.utils.versions
|
import salt.utils.versions
|
||||||
|
from salt.exceptions import CommandExecutionError
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
__virtualname__ = "dism"
|
__virtualname__ = "dism"
|
||||||
|
@ -517,12 +518,13 @@ def remove_package(package, image=None, restart=False):
|
||||||
Args:
|
Args:
|
||||||
package (str): The full path to the package. Can be either a .cab file
|
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
|
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
|
to where the file is installed. This can also be the name of a
|
||||||
``dism.installed_packages``
|
package as listed in ``dism.installed_packages``
|
||||||
image (Optional[str]): The path to the root directory of an offline
|
image (Optional[str]): The path to the root directory of an offline
|
||||||
Windows image. If `None` is passed, the running operating system is
|
Windows image. If `None` is passed, the running operating system is
|
||||||
targeted. Default is None.
|
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:
|
Returns:
|
||||||
dict: A dictionary containing the results of the command
|
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)
|
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):
|
def installed_packages(image=None):
|
||||||
"""
|
"""
|
||||||
List the packages installed on the system
|
List the packages installed on the system
|
||||||
|
@ -573,7 +642,12 @@ def installed_packages(image=None):
|
||||||
|
|
||||||
salt '*' dism.installed_packages
|
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):
|
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
|
image (Optional[str]): The path to the root directory of an offline
|
||||||
Windows image. If `None` is passed, the running operating system is
|
Windows image. If `None` is passed, the running operating system is
|
||||||
targeted. Default is None.
|
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:
|
Example:
|
||||||
Run ``dism.installed_capabilities`` to get a list of installed
|
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
|
image (Optional[str]): The path to the root directory of an offline
|
||||||
Windows image. If `None` is passed, the running operating system is
|
Windows image. If `None` is passed, the running operating system is
|
||||||
targeted. Default is None.
|
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:
|
Example:
|
||||||
Run ``dism.installed_features`` to get a list of installed features.
|
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
|
image (Optional[str]): The path to the root directory of an offline
|
||||||
Windows image. If `None` is passed, the running operating system is
|
Windows image. If `None` is passed, the running operating system is
|
||||||
targeted. Default is None.
|
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:
|
Example:
|
||||||
|
|
||||||
|
@ -414,3 +417,66 @@ def package_removed(name, image=None, restart=False):
|
||||||
ret["changes"]["package"] = changes
|
ret["changes"]["package"] = changes
|
||||||
|
|
||||||
return ret
|
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 pytest
|
||||||
|
|
||||||
import salt.modules.win_dism as dism
|
import salt.modules.win_dism as dism
|
||||||
|
from salt.exceptions import CommandExecutionError
|
||||||
from tests.support.mock import MagicMock, patch
|
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():
|
def test_installed_packages():
|
||||||
"""
|
"""
|
||||||
Test getting all the installed features
|
Test getting all the installed features
|
||||||
|
|
|
@ -548,3 +548,136 @@ def test_package_removed_removed():
|
||||||
mock_removed.assert_called_once_with()
|
mock_removed.assert_called_once_with()
|
||||||
assert not mock_remove.called
|
assert not mock_remove.called
|
||||||
assert out == expected
|
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