Added DISM module and states

- Allows you to install features and capabilities on windows minions
This commit is contained in:
Daniel Hobley 2016-01-29 10:35:58 +01:00
parent a2c260e701
commit e8c3a154f0
4 changed files with 509 additions and 0 deletions

157
salt/modules/win_dism.py Normal file
View 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
View 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

View 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)

View 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)