mirror of
https://github.com/valitydev/salt.git
synced 2024-11-06 16:45:27 +00:00
Added DISM module and states
- Allows you to install features and capabilities on windows minions
This commit is contained in:
parent
a2c260e701
commit
e8c3a154f0
157
salt/modules/win_dism.py
Normal file
157
salt/modules/win_dism.py
Normal file
@ -0,0 +1,157 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
'''
|
||||
Install features/packages for Windows using DISM, which
|
||||
is useful for minions not running server versions of
|
||||
windows
|
||||
|
||||
.. versionadded:: Beryllium
|
||||
'''
|
||||
|
||||
# Import python libs
|
||||
import re
|
||||
import logging
|
||||
|
||||
# Import salt libs
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
__virtualname__ = "dism"
|
||||
|
||||
|
||||
def __virtual__():
|
||||
'''
|
||||
Only work on Windows
|
||||
'''
|
||||
if salt.utils.is_windows():
|
||||
return __virtualname__
|
||||
return False
|
||||
|
||||
|
||||
def _install_component(name, component_type, install_name, source=None, limit_access=False):
|
||||
cmd = 'DISM /Online /{0}-{1} /{1}Name:{2}'.format(install_name, component_type, name)
|
||||
if source:
|
||||
cmd += ' /Source:{0}'.format(source)
|
||||
if limit_access:
|
||||
cmd += ' /LimitAccess'
|
||||
|
||||
return __salt__['cmd.run_all'](cmd)
|
||||
|
||||
|
||||
def _uninstall_component(name, component_type, uninstall_name):
|
||||
cmd = 'DISM /Online /{0}-{1} /{1}Name:{2}'.format(uninstall_name, component_type, name)
|
||||
|
||||
return __salt__['cmd.run_all'](cmd)
|
||||
|
||||
|
||||
def _get_components(type_regex, plural_type, install_value):
|
||||
cmd = 'DISM /Online /Get-{0}'.format(plural_type)
|
||||
out = __salt__['cmd.run'](cmd)
|
||||
pattern = r'{0} : (.*)\r\n.*State : {1}\r\n'.format(type_regex, install_value)
|
||||
capabilities = re.findall(pattern, out, re.MULTILINE)
|
||||
return capabilities
|
||||
|
||||
|
||||
def install_capability(capability, source=None, limit_access=False):
|
||||
'''
|
||||
Install a capability
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' dism.install_capability Tools.Graphics.DirectX~~~~0.0.1.0
|
||||
|
||||
capability
|
||||
The capability in which to install
|
||||
|
||||
source
|
||||
The optional source of the capability
|
||||
|
||||
limit_access
|
||||
Prevent DISM from contacting Windows Update for online images
|
||||
|
||||
'''
|
||||
return _install_component(capability, "Capability", "Add", source, limit_access)
|
||||
|
||||
|
||||
def uninstall_capability(capability):
|
||||
'''
|
||||
Uninstall a capability
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' dism.uninstall_capability Tools.Graphics.DirectX~~~~0.0.1.0
|
||||
|
||||
capability
|
||||
The capability in which to uninstall
|
||||
|
||||
'''
|
||||
return _uninstall_component(capability, "Capability", "Remove")
|
||||
|
||||
|
||||
def installed_capabilities():
|
||||
'''
|
||||
List the capabilities installed on the system
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' dism.installed_capabilities
|
||||
'''
|
||||
return _get_components("Capability Identity", "Capabilities", "Installed")
|
||||
|
||||
|
||||
def install_feature(capability, source=None, limit_access=False):
|
||||
'''
|
||||
Install a feature using DISM
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' dism.install_feature NetFx3
|
||||
|
||||
feature
|
||||
The feature in which to install
|
||||
|
||||
source
|
||||
The optional source of the feature
|
||||
|
||||
limit_access
|
||||
Prevent DISM from contacting Windows Update for online images
|
||||
|
||||
'''
|
||||
return _install_component(capability, "Feature", "Enable", source, limit_access)
|
||||
|
||||
|
||||
def uninstall_feature(capability):
|
||||
'''
|
||||
Uninstall a feature
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' dism.uninstall_feature NetFx3
|
||||
|
||||
feature
|
||||
The feature in which to uninstall
|
||||
|
||||
'''
|
||||
return _uninstall_component(capability, "Feature", "Disable")
|
||||
|
||||
|
||||
def installed_features():
|
||||
'''
|
||||
List the features installed on the system
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' dism.installed_features
|
||||
'''
|
||||
return _get_components("Feature Name", "Features", "Enabled")
|
108
salt/states/win_dism.py
Normal file
108
salt/states/win_dism.py
Normal file
@ -0,0 +1,108 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
'''
|
||||
Installing of Windows features using DISM
|
||||
=======================
|
||||
|
||||
Install windows features/capabilties with DISM
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
Language.Basic~~~en-US~0.0.1.0:
|
||||
dism.capability_installed
|
||||
|
||||
NetFx3:
|
||||
dism.feature_installed
|
||||
'''
|
||||
|
||||
# Import python libs
|
||||
import logging
|
||||
|
||||
# Import salt libs
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
__virtualname__ = "dism"
|
||||
|
||||
|
||||
def __virtual__():
|
||||
'''
|
||||
Only work on Windows
|
||||
'''
|
||||
if salt.utils.is_windows() and 'dism.install_capability' in __salt__:
|
||||
return __virtualname__
|
||||
return False
|
||||
|
||||
|
||||
def capability_installed(name, source=None, limit_access=False):
|
||||
'''
|
||||
Install a DISM capability
|
||||
|
||||
name
|
||||
The capability in which to install
|
||||
|
||||
source
|
||||
The optional source of the capability
|
||||
|
||||
limit_access
|
||||
Prevent DISM from contacting Windows Update for online images
|
||||
|
||||
'''
|
||||
ret = {'name': name,
|
||||
'result': True,
|
||||
'comment': '',
|
||||
'changes': {}}
|
||||
|
||||
comment = []
|
||||
|
||||
installed_capabilities = __salt__['dism.installed_capabilities']()
|
||||
|
||||
if name in installed_capabilities:
|
||||
comment.append("{0} was already installed.\n".format(name))
|
||||
else:
|
||||
out = __salt__['dism.install_capability'](name, source, limit_access)
|
||||
if out['retcode'] == 0:
|
||||
comment.append("{0} was installed.\n".format(name))
|
||||
ret['changes']['installed'] = name
|
||||
else:
|
||||
comment.append("{0} was unable to be installed. {1}\n".format(name, out['stdout']))
|
||||
ret['result'] = False
|
||||
|
||||
ret['comment'] = ' '.join(comment)
|
||||
return ret
|
||||
|
||||
|
||||
def feature_installed(name, source=None, limit_access=False):
|
||||
'''
|
||||
Install a DISM feature
|
||||
|
||||
name
|
||||
The feature in which to install
|
||||
|
||||
source
|
||||
The optional source of the feature
|
||||
|
||||
limit_access
|
||||
Prevent DISM from contacting Windows Update for online images
|
||||
|
||||
'''
|
||||
ret = {'name': name,
|
||||
'result': True,
|
||||
'comment': '',
|
||||
'changes': {}}
|
||||
|
||||
comment = []
|
||||
|
||||
installed_features = __salt__['dism.installed_features']()
|
||||
|
||||
if name in installed_features:
|
||||
comment.append("{0} was already installed.\n".format(name))
|
||||
else:
|
||||
out = __salt__['dism.install_feature'](name, source, limit_access)
|
||||
if out['retcode'] == 0:
|
||||
comment.append("{0} was installed.\n".format(name))
|
||||
ret['changes']['installed'] = name
|
||||
else:
|
||||
comment.append("{0} was unable to be installed. {1}\n".format(name, out['stdout']))
|
||||
ret['result'] = False
|
||||
|
||||
ret['comment'] = ' '.join(comment)
|
||||
return ret
|
97
tests/unit/modules/win_dism_test.py
Normal file
97
tests/unit/modules/win_dism_test.py
Normal file
@ -0,0 +1,97 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Import Python libs
|
||||
from __future__ import absolute_import
|
||||
|
||||
# Import Salt Libs
|
||||
from salt.modules import win_dism as dism
|
||||
|
||||
# Import Salt Testing Libs
|
||||
from salttesting import TestCase
|
||||
from salttesting.helpers import ensure_in_syspath
|
||||
from salttesting.mock import (
|
||||
MagicMock,
|
||||
patch
|
||||
)
|
||||
|
||||
ensure_in_syspath('../../')
|
||||
|
||||
dism.__salt__ = {}
|
||||
|
||||
|
||||
class DISMTestCase(TestCase):
|
||||
|
||||
def test_install_capability(self):
|
||||
'''
|
||||
Test installing a capability with DISM
|
||||
'''
|
||||
mock = MagicMock()
|
||||
with patch.dict(dism.__salt__, {'cmd.run_all': mock}):
|
||||
dism.install_capability("test")
|
||||
mock.assert_called_once_with('DISM /Online /Add-Capability /CapabilityName:test')
|
||||
|
||||
def test_install_capability_with_extras(self):
|
||||
'''
|
||||
Test installing a capability with DISM
|
||||
'''
|
||||
mock = MagicMock()
|
||||
with patch.dict(dism.__salt__, {'cmd.run_all': mock}):
|
||||
dism.install_capability("test", "life", True)
|
||||
mock.assert_called_once_with('DISM /Online /Add-Capability /CapabilityName:test /Source:life /LimitAccess')
|
||||
|
||||
def test_uninstall_capability(self):
|
||||
'''
|
||||
Test uninstalling a capability with DISM
|
||||
'''
|
||||
mock = MagicMock()
|
||||
with patch.dict(dism.__salt__, {'cmd.run_all': mock}):
|
||||
dism.uninstall_capability("test")
|
||||
mock.assert_called_once_with('DISM /Online /Remove-Capability /CapabilityName:test')
|
||||
|
||||
def test_installed_capabilities(self):
|
||||
'''
|
||||
Test getting all the installed capabilities
|
||||
'''
|
||||
capabilties = "Capability Identity : Capa1\r\n State : Installed\r\n" \
|
||||
"Capability Identity : Capa2\r\n State : Disabled\r\n"
|
||||
|
||||
mock = MagicMock(return_value=capabilties)
|
||||
with patch.dict(dism.__salt__, {'cmd.run': mock}):
|
||||
out = dism.installed_capabilities()
|
||||
mock.assert_called_once_with('DISM /Online /Get-Capabilities')
|
||||
self.assertEqual(out, ["Capa1"])
|
||||
|
||||
def test_install_feature(self):
|
||||
'''
|
||||
Test installing a feature with DISM
|
||||
'''
|
||||
mock = MagicMock()
|
||||
with patch.dict(dism.__salt__, {'cmd.run_all': mock}):
|
||||
dism.install_feature("test")
|
||||
mock.assert_called_once_with('DISM /Online /Enable-Feature /FeatureName:test')
|
||||
|
||||
def test_uninstall_feature(self):
|
||||
'''
|
||||
Test uninstalling a capability with DISM
|
||||
'''
|
||||
mock = MagicMock()
|
||||
with patch.dict(dism.__salt__, {'cmd.run_all': mock}):
|
||||
dism.uninstall_feature("test")
|
||||
mock.assert_called_once_with('DISM /Online /Disable-Feature /FeatureName:test')
|
||||
|
||||
def test_installed_feature(self):
|
||||
'''
|
||||
Test getting all the installed capabilities
|
||||
'''
|
||||
capabilties = "Feature Name : Capa1\r\n State : Enabled\r\n" \
|
||||
"Feature Name : Capa2\r\n State : Disabled\r\n"
|
||||
|
||||
mock = MagicMock(return_value=capabilties)
|
||||
with patch.dict(dism.__salt__, {'cmd.run': mock}):
|
||||
out = dism.installed_features()
|
||||
mock.assert_called_once_with('DISM /Online /Get-Features')
|
||||
self.assertEqual(out, ["Capa1"])
|
||||
|
||||
if __name__ == '__main__':
|
||||
from integration import run_tests
|
||||
run_tests(DISMTestCase, needs_daemon=False)
|
147
tests/unit/states/win_dism_test.py
Normal file
147
tests/unit/states/win_dism_test.py
Normal file
@ -0,0 +1,147 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Import Python libs
|
||||
from __future__ import absolute_import
|
||||
|
||||
# Import Salt Libs
|
||||
from salt.states import win_dism as dism
|
||||
|
||||
# Import Salt Testing Libs
|
||||
from salttesting import TestCase
|
||||
from salttesting.helpers import ensure_in_syspath
|
||||
from salttesting.mock import (
|
||||
MagicMock,
|
||||
patch
|
||||
)
|
||||
|
||||
ensure_in_syspath('../../')
|
||||
|
||||
dism.__salt__ = {}
|
||||
|
||||
|
||||
class DISMTestCase(TestCase):
|
||||
|
||||
def test_install_capability(self):
|
||||
'''
|
||||
Test installing a capability with DISM
|
||||
'''
|
||||
expected = {
|
||||
'comment': "Capa2 was installed.\n",
|
||||
'changes': {'installed': 'Capa2'},
|
||||
'name': 'Capa2',
|
||||
'result': True
|
||||
}
|
||||
|
||||
installed_mock = MagicMock(return_value=["Capa1"])
|
||||
install_mock = MagicMock(return_value={'retcode': 0})
|
||||
with patch.dict(dism.__salt__, {'dism.installed_capabilities': installed_mock,
|
||||
'dism.install_capability': install_mock}):
|
||||
out = dism.capability_installed('Capa2', 'somewhere', True)
|
||||
installed_mock.assert_called_once_with()
|
||||
install_mock.assert_called_once_with('Capa2', 'somewhere', True)
|
||||
self.assertEqual(out, expected)
|
||||
|
||||
def test_install_capability_failure(self):
|
||||
'''
|
||||
Test installing a capability which fails with DISM
|
||||
'''
|
||||
expected = {
|
||||
'comment': "Capa2 was unable to be installed. Failed\n",
|
||||
'changes': {},
|
||||
'name': 'Capa2',
|
||||
'result': False
|
||||
}
|
||||
|
||||
installed_mock = MagicMock(return_value=["Capa1"])
|
||||
install_mock = MagicMock(return_value={'retcode': 67, 'stdout': 'Failed'})
|
||||
with patch.dict(dism.__salt__, {'dism.installed_capabilities': installed_mock,
|
||||
'dism.install_capability': install_mock}):
|
||||
out = dism.capability_installed('Capa2', 'somewhere', True)
|
||||
installed_mock.assert_called_once_with()
|
||||
install_mock.assert_called_once_with('Capa2', 'somewhere', True)
|
||||
self.assertEqual(out, expected)
|
||||
|
||||
def test_installed_capability(self):
|
||||
'''
|
||||
Test installing a capability already installed
|
||||
'''
|
||||
expected = {
|
||||
'comment': "Capa2 was already installed.\n",
|
||||
'changes': {},
|
||||
'name': 'Capa2',
|
||||
'result': True
|
||||
}
|
||||
|
||||
installed_mock = MagicMock(return_value=["Capa1", "Capa2"])
|
||||
install_mock = MagicMock()
|
||||
with patch.dict(dism.__salt__, {'dism.installed_capabilities': installed_mock,
|
||||
'dism.install_capability': install_mock}):
|
||||
out = dism.capability_installed('Capa2', 'somewhere', True)
|
||||
installed_mock.assert_called_once_with()
|
||||
assert not install_mock.called
|
||||
self.assertEqual(out, expected)
|
||||
|
||||
def test_install_feature(self):
|
||||
'''
|
||||
Test installing a feature with DISM
|
||||
'''
|
||||
expected = {
|
||||
'comment': "Feat1 was installed.\n",
|
||||
'changes': {'installed': 'Feat1'},
|
||||
'name': 'Feat1',
|
||||
'result': True
|
||||
}
|
||||
|
||||
installed_mock = MagicMock(return_value=["Feat2"])
|
||||
install_mock = MagicMock(return_value={'retcode': 0})
|
||||
with patch.dict(dism.__salt__, {'dism.installed_features': installed_mock,
|
||||
'dism.install_feature': install_mock}):
|
||||
out = dism.feature_installed('Feat1', 'somewhere', True)
|
||||
installed_mock.assert_called_once_with()
|
||||
install_mock.assert_called_once_with('Feat1', 'somewhere', True)
|
||||
self.assertEqual(out, expected)
|
||||
|
||||
def test_install_feature_failure(self):
|
||||
'''
|
||||
Test installing a feature which fails with DISM
|
||||
'''
|
||||
expected = {
|
||||
'comment': "Feat1 was unable to be installed. Failed\n",
|
||||
'changes': {},
|
||||
'name': 'Feat1',
|
||||
'result': False
|
||||
}
|
||||
|
||||
installed_mock = MagicMock(return_value=["Feat3"])
|
||||
install_mock = MagicMock(return_value={'retcode': 67, 'stdout': 'Failed'})
|
||||
with patch.dict(dism.__salt__, {'dism.installed_features': installed_mock,
|
||||
'dism.install_feature': install_mock}):
|
||||
out = dism.feature_installed('Feat1', 'somewhere', True)
|
||||
installed_mock.assert_called_once_with()
|
||||
install_mock.assert_called_once_with('Feat1', 'somewhere', True)
|
||||
self.assertEqual(out, expected)
|
||||
|
||||
def test_installed_feature(self):
|
||||
'''
|
||||
Test installing a feature already installed
|
||||
'''
|
||||
expected = {
|
||||
'comment': "Feat1 was already installed.\n",
|
||||
'changes': {},
|
||||
'name': 'Feat1',
|
||||
'result': True
|
||||
}
|
||||
|
||||
installed_mock = MagicMock(return_value=["Feat1", "Feat2"])
|
||||
install_mock = MagicMock()
|
||||
with patch.dict(dism.__salt__, {'dism.installed_features': installed_mock,
|
||||
'dism.install_feature': install_mock}):
|
||||
out = dism.feature_installed('Feat1', 'somewhere', True)
|
||||
installed_mock.assert_called_once_with()
|
||||
assert not install_mock.called
|
||||
self.assertEqual(out, expected)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
from integration import run_tests
|
||||
run_tests(DISMTestCase, needs_daemon=False)
|
Loading…
Reference in New Issue
Block a user