Merge pull request #54879 from Akm0d/master_webconfig_settings

Master webconfig settings
This commit is contained in:
Daniel Wozniak 2019-11-01 17:28:50 -07:00 committed by GitHub
commit c41692654a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 681 additions and 88 deletions

View File

@ -13,12 +13,14 @@ Microsoft IIS site management via WebAdministration powershell module
from __future__ import absolute_import, print_function, unicode_literals
import decimal
import logging
import re
import os
import yaml
# Import salt libs
import salt.utils.json
import salt.utils.platform
from salt.ext.six.moves import range
from salt.ext.six.moves import range, map
from salt.exceptions import SaltInvocationError, CommandExecutionError
from salt.ext import six
@ -160,6 +162,45 @@ def _srvmgr(cmd, return_json=False):
return ret
def _collection_match_to_index(pspath, colfilter, name, match):
'''
Returns index of collection item matching the match dictionary.
'''
collection = get_webconfiguration_settings(pspath, [{'name': name, 'filter': colfilter}])[0]['value']
for idx, collect_dict in enumerate(collection):
if all(item in collect_dict.items() for item in match.items()):
return idx
return -1
def _prepare_settings(pspath, settings):
'''
Prepare settings before execution with get or set functions.
Removes settings with a match parameter when index is not found.
'''
prepared_settings = []
for setting in settings:
if setting.get('name', None) is None:
log.warning('win_iis: Setting has no name: {}'.format(setting))
continue
if setting.get('filter', None) is None:
log.warning('win_iis: Setting has no filter: {}'.format(setting))
continue
match = re.search(r'Collection\[(\{.*\})\]', setting['name'])
if match:
name = setting['name'][:match.start(1)-1]
match_dict = yaml.load(match.group(1))
index = _collection_match_to_index(pspath, setting['filter'], name, match_dict)
if index == -1:
log.warning('win_iis: No match found for setting: {}'.format(setting))
else:
setting['name'] = setting['name'].replace(match.group(1), str(index))
prepared_settings.append(setting)
else:
prepared_settings.append(setting)
return prepared_settings
def list_sites():
'''
List all the currently deployed websites.
@ -1985,3 +2026,167 @@ def set_webapp_settings(name, site, settings):
log.debug('Settings configured successfully: {0}'.format(settings.keys()))
return True
def get_webconfiguration_settings(name, settings):
r'''
Get the webconfiguration settings for the IIS PSPath.
Args:
name (str): The PSPath of the IIS webconfiguration settings.
settings (list): A list of dictionaries containing setting name and filter.
Returns:
dict: A list of dictionaries containing setting name, filter and value.
CLI Example:
.. code-block:: bash
salt '*' win_iis.get_webconfiguration_settings name='IIS:\' settings="[{'name': 'enabled', 'filter': 'system.webServer/security/authentication/anonymousAuthentication'}]"
'''
ret = {}
ps_cmd = [r'$Settings = New-Object System.Collections.ArrayList;']
ps_cmd_validate = []
settings = _prepare_settings(name, settings)
if not settings:
log.warning('No settings provided')
return ret
for setting in settings:
# Build the commands to verify that the property names are valid.
ps_cmd_validate.extend(['Get-WebConfigurationProperty',
'-PSPath', "'{0}'".format(name),
'-Filter', "'{0}'".format(setting['filter']),
'-Name', "'{0}'".format(setting['name']),
'-ErrorAction', 'Stop',
'|', 'Out-Null;'])
# Some ItemProperties are Strings and others are ConfigurationAttributes.
# Since the former doesn't have a Value property, we need to account
# for this.
ps_cmd.append("$Property = Get-WebConfigurationProperty -PSPath '{0}'".format(name))
ps_cmd.append("-Name '{0}' -Filter '{1}' -ErrorAction Stop;".format(setting['name'], setting['filter']))
if setting['name'].split('.')[-1] == 'Collection':
if 'value' in setting:
ps_cmd.append("$Property = $Property | select -Property {0} ;"
.format(",".join(list(setting['value'][0].keys()))))
ps_cmd.append("$Settings.add(@{{filter='{0}';name='{1}';value=[System.Collections.ArrayList] @($Property)}})| Out-Null;"
.format(setting['filter'], setting['name']))
else:
ps_cmd.append(r'if (([String]::IsNullOrEmpty($Property) -eq $False) -and')
ps_cmd.append(r"($Property.GetType()).Name -eq 'ConfigurationAttribute') {")
ps_cmd.append(r'$Property = $Property | Select-Object')
ps_cmd.append(r'-ExpandProperty Value };')
ps_cmd.append("$Settings.add(@{{filter='{0}';name='{1}';value=[String] $Property}})| Out-Null;"
.format(setting['filter'], setting['name']))
ps_cmd.append(r'$Property = $Null;')
# Validate the setting names that were passed in.
cmd_ret = _srvmgr(cmd=ps_cmd_validate, return_json=True)
if cmd_ret['retcode'] != 0:
message = 'One or more invalid property names were specified for the provided container.'
raise SaltInvocationError(message)
ps_cmd.append('$Settings')
cmd_ret = _srvmgr(cmd=ps_cmd, return_json=True)
try:
ret = salt.utils.json.loads(cmd_ret['stdout'], strict=False)
except ValueError:
raise CommandExecutionError('Unable to parse return data as Json.')
return ret
def set_webconfiguration_settings(name, settings):
r'''
Set the value of the setting for an IIS container.
Args:
name (str): The PSPath of the IIS webconfiguration settings.
settings (list): A list of dictionaries containing setting name, filter and value.
Returns:
bool: True if successful, otherwise False
CLI Example:
.. code-block:: bash
salt '*' win_iis.set_webconfiguration_settings name='IIS:\' settings="[{'name': 'enabled', 'filter': 'system.webServer/security/authentication/anonymousAuthentication', 'value': False}]"
'''
ps_cmd = []
settings = _prepare_settings(name, settings)
if not settings:
log.warning('No settings provided')
return False
# Treat all values as strings for the purpose of comparing them to existing values.
for idx, setting in enumerate(settings):
if setting['name'].split('.')[-1] != 'Collection':
settings[idx]['value'] = six.text_type(setting['value'])
current_settings = get_webconfiguration_settings(
name=name, settings=settings)
if settings == current_settings:
log.debug('Settings already contain the provided values.')
return True
for setting in settings:
# If the value is numeric, don't treat it as a string in PowerShell.
if setting['name'].split('.')[-1] != 'Collection':
try:
complex(setting['value'])
value = setting['value']
except ValueError:
value = "'{0}'".format(setting['value'])
else:
configelement_list = []
for value_item in setting['value']:
configelement_construct = []
for key, value in value_item.items():
configelement_construct.append("{0}='{1}'".format(key, value))
configelement_list.append('@{' + ';'.join(configelement_construct) + '}')
value = ','.join(configelement_list)
ps_cmd.extend(['Set-WebConfigurationProperty',
'-PSPath', "'{0}'".format(name),
'-Filter', "'{0}'".format(setting['filter']),
'-Name', "'{0}'".format(setting['name']),
'-Value', '{0};'.format(value)])
cmd_ret = _srvmgr(ps_cmd)
if cmd_ret['retcode'] != 0:
msg = 'Unable to set settings for {0}'.format(name)
raise CommandExecutionError(msg)
# Get the fields post-change so that we can verify tht all values
# were modified successfully. Track the ones that weren't.
new_settings = get_webconfiguration_settings(
name=name, settings=settings)
failed_settings = []
for idx, setting in enumerate(settings):
is_collection = setting['name'].split('.')[-1] == 'Collection'
if ((not is_collection and six.text_type(setting['value']) != six.text_type(new_settings[idx]['value']))
or (is_collection and list(map(dict, setting['value'])) != list(map(dict, new_settings[idx]['value'])))):
failed_settings.append(setting)
if failed_settings:
log.error('Failed to change settings: %s', failed_settings)
return False
log.debug('Settings configured successfully: %s', settings)
return True

View File

@ -11,6 +11,7 @@ from Microsoft IIS.
# Import python libs
from __future__ import absolute_import, unicode_literals, print_function
from salt.ext.six.moves import map
# Define the module's virtual name
@ -865,3 +866,125 @@ def set_app(name, site, settings=None):
ret['result'] = True
return ret
def webconfiguration_settings(name, settings=None):
r'''
Set the value of webconfiguration settings.
:param str name: The name of the IIS PSPath containing the settings.
Possible PSPaths are :
MACHINE, MACHINE/WEBROOT, IIS:\, IIS:\Sites\sitename, ...
:param dict settings: Dictionaries of dictionaries.
You can match a specific item in a collection with this syntax inside a key:
'Collection[{name: site0}].logFile.directory'
Example of usage for the ``MACHINE/WEBROOT`` PSPath:
.. code-block:: yaml
MACHINE-WEBROOT-level-security:
win_iis.webconfiguration_settings:
- name: 'MACHINE/WEBROOT'
- settings:
system.web/authentication/forms:
requireSSL: True
protection: "All"
credentials.passwordFormat: "SHA1"
system.web/httpCookies:
httpOnlyCookies: True
Example of usage for the ``IIS:\Sites\site0`` PSPath:
.. code-block:: yaml
site0-IIS-Sites-level-security:
win_iis.webconfiguration_settings:
- name: 'IIS:\Sites\site0'
- settings:
system.webServer/httpErrors:
errorMode: "DetailedLocalOnly"
system.webServer/security/requestFiltering:
allowDoubleEscaping: False
verbs.Collection:
- verb: TRACE
allowed: False
fileExtensions.allowUnlisted: False
Example of usage for the ``IIS:\`` PSPath with a collection matching:
.. code-block:: yaml
site0-IIS-level-security:
win_iis.webconfiguration_settings:
- name: 'IIS:\'
- settings:
system.applicationHost/sites:
'Collection[{name: site0}].logFile.directory': 'C:\logs\iis\site0'
'''
ret = {'name': name,
'changes': {},
'comment': str(),
'result': None}
if not settings:
ret['comment'] = 'No settings to change provided.'
ret['result'] = True
return ret
ret_settings = {
'changes': {},
'failures': {},
}
settings_list = list()
for filter, filter_settings in settings.items():
for setting_name, value in filter_settings.items():
settings_list.append({'filter': filter, 'name': setting_name, 'value': value})
current_settings_list = __salt__['win_iis.get_webconfiguration_settings'](name=name, settings=settings_list)
for idx, setting in enumerate(settings_list):
is_collection = setting['name'].split('.')[-1] == 'Collection'
# If this is a new setting and not an update to an existing setting
if len(current_settings_list) <= idx:
ret_settings['changes'][setting['filter'] + '.' + setting['name']] = {'old': {},
'new': settings_list[idx]['value']}
elif ((is_collection and list(map(dict, setting['value'])) != list(map(dict, current_settings_list[idx]['value'])))
or (not is_collection and str(setting['value']) != str(current_settings_list[idx]['value']))):
ret_settings['changes'][setting['filter'] + '.' + setting['name']] = {'old': current_settings_list[idx]['value'],
'new': settings_list[idx]['value']}
if not ret_settings['changes']:
ret['comment'] = 'Settings already contain the provided values.'
ret['result'] = True
return ret
elif __opts__['test']:
ret['comment'] = 'Settings will be changed.'
ret['changes'] = ret_settings
return ret
success = __salt__['win_iis.set_webconfiguration_settings'](name=name, settings=settings_list)
new_settings_list = __salt__['win_iis.get_webconfiguration_settings'](name=name, settings=settings_list)
for idx, setting in enumerate(settings_list):
is_collection = setting['name'].split('.')[-1] == 'Collection'
if ((is_collection and setting['value'] != new_settings_list[idx]['value'])
or (not is_collection and str(setting['value']) != str(new_settings_list[idx]['value']))):
ret_settings['failures'][setting['filter'] + '.' + setting['name']] = {'old': current_settings_list[idx]['value'],
'new': new_settings_list[idx]['value']}
ret_settings['changes'].get(setting['filter'] + '.' + setting['name'], None)
if ret_settings['failures']:
ret['comment'] = 'Some settings failed to change.'
ret['changes'] = ret_settings
ret['result'] = False
else:
ret['comment'] = 'Set settings to contain the provided values.'
ret['changes'] = ret_settings['changes']
ret['result'] = success
return ret

View File

@ -131,9 +131,9 @@ class WinIisTestCase(TestCase, LoaderModuleMockMixin):
'''
with patch('salt.modules.win_iis._srvmgr',
MagicMock(return_value={'retcode': 0})), \
patch('salt.modules.win_iis.list_apppools',
MagicMock(return_value=dict())), \
patch.dict(win_iis.__salt__):
patch('salt.modules.win_iis.list_apppools',
MagicMock(return_value=dict())), \
patch.dict(win_iis.__salt__):
self.assertTrue(win_iis.create_apppool('MyTestPool'))
def test_list_apppools(self):
@ -141,8 +141,8 @@ class WinIisTestCase(TestCase, LoaderModuleMockMixin):
Test - List all configured IIS application pools.
'''
with patch.dict(win_iis.__salt__), \
patch('salt.modules.win_iis._srvmgr',
MagicMock(return_value=LIST_APPPOOLS_SRVMGR)):
patch('salt.modules.win_iis._srvmgr',
MagicMock(return_value=LIST_APPPOOLS_SRVMGR)):
self.assertEqual(win_iis.list_apppools(), APPPOOL_LIST)
def test_remove_apppool(self):
@ -150,12 +150,12 @@ class WinIisTestCase(TestCase, LoaderModuleMockMixin):
Test - Remove an IIS application pool.
'''
with patch.dict(win_iis.__salt__), \
patch('salt.modules.win_iis._srvmgr',
MagicMock(return_value={'retcode': 0})), \
patch('salt.modules.win_iis.list_apppools',
MagicMock(return_value={'MyTestPool': {
'applications': list(),
'state': 'Started'}})):
patch('salt.modules.win_iis._srvmgr',
MagicMock(return_value={'retcode': 0})), \
patch('salt.modules.win_iis.list_apppools',
MagicMock(return_value={'MyTestPool': {
'applications': list(),
'state': 'Started'}})):
self.assertTrue(win_iis.remove_apppool('MyTestPool'))
def test_restart_apppool(self):
@ -163,8 +163,8 @@ class WinIisTestCase(TestCase, LoaderModuleMockMixin):
Test - Restart an IIS application pool.
'''
with patch.dict(win_iis.__salt__), \
patch('salt.modules.win_iis._srvmgr',
MagicMock(return_value={'retcode': 0})):
patch('salt.modules.win_iis._srvmgr',
MagicMock(return_value={'retcode': 0})):
self.assertTrue(win_iis.restart_apppool('MyTestPool'))
def test_create_site(self):
@ -175,12 +175,12 @@ class WinIisTestCase(TestCase, LoaderModuleMockMixin):
'apppool': 'MyTestPool', 'hostheader': 'mytestsite.local',
'ipaddress': '*', 'port': 80, 'protocol': 'http'}
with patch.dict(win_iis.__salt__), \
patch('salt.modules.win_iis._srvmgr',
MagicMock(return_value={'retcode': 0})), \
patch('salt.modules.win_iis.list_sites',
MagicMock(return_value=dict())), \
patch('salt.modules.win_iis.list_apppools',
MagicMock(return_value=dict())):
patch('salt.modules.win_iis._srvmgr',
MagicMock(return_value={'retcode': 0})), \
patch('salt.modules.win_iis.list_sites',
MagicMock(return_value=dict())), \
patch('salt.modules.win_iis.list_apppools',
MagicMock(return_value=dict())):
self.assertTrue(win_iis.create_site(**kwargs))
def test_create_site_failed(self):
@ -191,12 +191,12 @@ class WinIisTestCase(TestCase, LoaderModuleMockMixin):
'apppool': 'MyTestPool', 'hostheader': 'mytestsite.local',
'ipaddress': '*', 'port': 80, 'protocol': 'invalid-protocol-name'}
with patch.dict(win_iis.__salt__), \
patch('salt.modules.win_iis._srvmgr',
MagicMock(return_value={'retcode': 0})), \
patch('salt.modules.win_iis.list_sites',
MagicMock(return_value=dict())), \
patch('salt.modules.win_iis.list_apppools',
MagicMock(return_value=dict())):
patch('salt.modules.win_iis._srvmgr',
MagicMock(return_value={'retcode': 0})), \
patch('salt.modules.win_iis.list_sites',
MagicMock(return_value=dict())), \
patch('salt.modules.win_iis.list_apppools',
MagicMock(return_value=dict())):
self.assertRaises(SaltInvocationError, win_iis.create_site, **kwargs)
def test_remove_site(self):
@ -204,10 +204,10 @@ class WinIisTestCase(TestCase, LoaderModuleMockMixin):
Test - Delete a website from IIS.
'''
with patch.dict(win_iis.__salt__), \
patch('salt.modules.win_iis._srvmgr',
MagicMock(return_value={'retcode': 0})), \
patch('salt.modules.win_iis.list_sites',
MagicMock(return_value=SITE_LIST)):
patch('salt.modules.win_iis._srvmgr',
MagicMock(return_value={'retcode': 0})), \
patch('salt.modules.win_iis.list_sites',
MagicMock(return_value=SITE_LIST)):
self.assertTrue(win_iis.remove_site('MyTestSite'))
def test_create_app(self):
@ -217,11 +217,11 @@ class WinIisTestCase(TestCase, LoaderModuleMockMixin):
kwargs = {'name': 'testApp', 'site': 'MyTestSite',
'sourcepath': r'C:\inetpub\apps\testApp', 'apppool': 'MyTestPool'}
with patch.dict(win_iis.__salt__), \
patch('os.path.isdir', MagicMock(return_value=True)), \
patch('salt.modules.win_iis._srvmgr',
MagicMock(return_value={'retcode': 0})), \
patch('salt.modules.win_iis.list_apps',
MagicMock(return_value=APP_LIST)):
patch('os.path.isdir', MagicMock(return_value=True)), \
patch('salt.modules.win_iis._srvmgr',
MagicMock(return_value={'retcode': 0})), \
patch('salt.modules.win_iis.list_apps',
MagicMock(return_value=APP_LIST)):
self.assertTrue(win_iis.create_app(**kwargs))
def test_list_apps(self):
@ -229,8 +229,8 @@ class WinIisTestCase(TestCase, LoaderModuleMockMixin):
Test - Get all configured IIS applications for the specified site.
'''
with patch.dict(win_iis.__salt__), \
patch('salt.modules.win_iis._srvmgr',
MagicMock(return_value=LIST_APPS_SRVMGR)):
patch('salt.modules.win_iis._srvmgr',
MagicMock(return_value=LIST_APPS_SRVMGR)):
self.assertEqual(win_iis.list_apps('MyTestSite'), APP_LIST)
def test_remove_app(self):
@ -239,10 +239,10 @@ class WinIisTestCase(TestCase, LoaderModuleMockMixin):
'''
kwargs = {'name': 'otherApp', 'site': 'MyTestSite'}
with patch.dict(win_iis.__salt__), \
patch('salt.modules.win_iis._srvmgr',
MagicMock(return_value={'retcode': 0})), \
patch('salt.modules.win_iis.list_apps',
MagicMock(return_value=APP_LIST)):
patch('salt.modules.win_iis._srvmgr',
MagicMock(return_value={'retcode': 0})), \
patch('salt.modules.win_iis.list_apps',
MagicMock(return_value=APP_LIST)):
self.assertTrue(win_iis.remove_app(**kwargs))
def test_create_binding(self):
@ -252,10 +252,10 @@ class WinIisTestCase(TestCase, LoaderModuleMockMixin):
kwargs = {'site': 'MyTestSite', 'hostheader': '', 'ipaddress': '*',
'port': 80, 'protocol': 'http', 'sslflags': 0}
with patch.dict(win_iis.__salt__), \
patch('salt.modules.win_iis._srvmgr',
MagicMock(return_value={'retcode': 0})), \
patch('salt.modules.win_iis.list_bindings',
MagicMock(return_value=BINDING_LIST)):
patch('salt.modules.win_iis._srvmgr',
MagicMock(return_value={'retcode': 0})), \
patch('salt.modules.win_iis.list_bindings',
MagicMock(return_value=BINDING_LIST)):
self.assertTrue(win_iis.create_binding(**kwargs))
def test_create_binding_failed(self):
@ -265,10 +265,10 @@ class WinIisTestCase(TestCase, LoaderModuleMockMixin):
kwargs = {'site': 'MyTestSite', 'hostheader': '', 'ipaddress': '*',
'port': 80, 'protocol': 'invalid-protocol-name', 'sslflags': 999}
with patch.dict(win_iis.__salt__), \
patch('salt.modules.win_iis._srvmgr',
MagicMock(return_value={'retcode': 0})), \
patch('salt.modules.win_iis.list_bindings',
MagicMock(return_value=BINDING_LIST)):
patch('salt.modules.win_iis._srvmgr',
MagicMock(return_value={'retcode': 0})), \
patch('salt.modules.win_iis.list_bindings',
MagicMock(return_value=BINDING_LIST)):
self.assertRaises(SaltInvocationError, win_iis.create_binding, **kwargs)
def test_list_bindings(self):
@ -276,8 +276,8 @@ class WinIisTestCase(TestCase, LoaderModuleMockMixin):
Test - Get all configured IIS bindings for the specified site.
'''
with patch.dict(win_iis.__salt__), \
patch('salt.modules.win_iis.list_sites',
MagicMock(return_value=SITE_LIST)):
patch('salt.modules.win_iis.list_sites',
MagicMock(return_value=SITE_LIST)):
self.assertEqual(win_iis.list_bindings('MyTestSite'), BINDING_LIST)
def test_remove_binding(self):
@ -287,10 +287,10 @@ class WinIisTestCase(TestCase, LoaderModuleMockMixin):
kwargs = {'site': 'MyTestSite', 'hostheader': 'myothertestsite.local',
'ipaddress': '*', 'port': 443}
with patch.dict(win_iis.__salt__), \
patch('salt.modules.win_iis._srvmgr',
MagicMock(return_value={'retcode': 0})), \
patch('salt.modules.win_iis.list_bindings',
MagicMock(return_value=BINDING_LIST)):
patch('salt.modules.win_iis._srvmgr',
MagicMock(return_value={'retcode': 0})), \
patch('salt.modules.win_iis.list_bindings',
MagicMock(return_value=BINDING_LIST)):
self.assertTrue(win_iis.remove_binding(**kwargs))
def test_create_vdir(self):
@ -300,12 +300,12 @@ class WinIisTestCase(TestCase, LoaderModuleMockMixin):
kwargs = {'name': 'TestVdir', 'site': 'MyTestSite',
'sourcepath': r'C:\inetpub\vdirs\TestVdir'}
with patch.dict(win_iis.__salt__), \
patch('os.path.isdir',
MagicMock(return_value=True)), \
patch('salt.modules.win_iis._srvmgr',
MagicMock(return_value={'retcode': 0})), \
patch('salt.modules.win_iis.list_vdirs',
MagicMock(return_value=VDIR_LIST)):
patch('os.path.isdir',
MagicMock(return_value=True)), \
patch('salt.modules.win_iis._srvmgr',
MagicMock(return_value={'retcode': 0})), \
patch('salt.modules.win_iis.list_vdirs',
MagicMock(return_value=VDIR_LIST)):
self.assertTrue(win_iis.create_vdir(**kwargs))
def test_list_vdirs(self):
@ -318,8 +318,8 @@ class WinIisTestCase(TestCase, LoaderModuleMockMixin):
}
}
with patch.dict(win_iis.__salt__), \
patch('salt.modules.win_iis._srvmgr',
MagicMock(return_value=LIST_VDIRS_SRVMGR)):
patch('salt.modules.win_iis._srvmgr',
MagicMock(return_value=LIST_VDIRS_SRVMGR)):
self.assertEqual(win_iis.list_vdirs('MyTestSite'), vdirs)
def test_remove_vdir(self):
@ -328,10 +328,10 @@ class WinIisTestCase(TestCase, LoaderModuleMockMixin):
'''
kwargs = {'name': 'TestOtherVdir', 'site': 'MyTestSite'}
with patch.dict(win_iis.__salt__), \
patch('salt.modules.win_iis._srvmgr',
MagicMock(return_value={'retcode': 0})), \
patch('salt.modules.win_iis.list_vdirs',
MagicMock(return_value=VDIR_LIST)):
patch('salt.modules.win_iis._srvmgr',
MagicMock(return_value={'retcode': 0})), \
patch('salt.modules.win_iis.list_vdirs',
MagicMock(return_value=VDIR_LIST)):
self.assertTrue(win_iis.remove_vdir(**kwargs))
def test_create_cert_binding(self):
@ -342,15 +342,15 @@ class WinIisTestCase(TestCase, LoaderModuleMockMixin):
'site': 'MyTestSite', 'hostheader': 'mytestsite.local',
'ipaddress': '*', 'port': 443}
with patch.dict(win_iis.__salt__), \
patch('salt.modules.win_iis._list_certs',
MagicMock(return_value={'9988776655443322111000AAABBBCCCDDDEEEFFF': None})), \
patch('salt.modules.win_iis._srvmgr',
MagicMock(return_value={'retcode': 0, 'stdout': 10})), \
patch('salt.utils.json.loads', MagicMock(return_value=[{'MajorVersion': 10, 'MinorVersion': 0}])), \
patch('salt.modules.win_iis.list_bindings',
MagicMock(return_value=BINDING_LIST)), \
patch('salt.modules.win_iis.list_cert_bindings',
MagicMock(return_value={CERT_BINDING_INFO: BINDING_LIST[CERT_BINDING_INFO]})):
patch('salt.modules.win_iis._list_certs',
MagicMock(return_value={'9988776655443322111000AAABBBCCCDDDEEEFFF': None})), \
patch('salt.modules.win_iis._srvmgr',
MagicMock(return_value={'retcode': 0, 'stdout': 10})), \
patch('salt.utils.json.loads', MagicMock(return_value=[{'MajorVersion': 10, 'MinorVersion': 0}])), \
patch('salt.modules.win_iis.list_bindings',
MagicMock(return_value=BINDING_LIST)), \
patch('salt.modules.win_iis.list_cert_bindings',
MagicMock(return_value={CERT_BINDING_INFO: BINDING_LIST[CERT_BINDING_INFO]})):
self.assertTrue(win_iis.create_cert_binding(**kwargs))
def test_list_cert_bindings(self):
@ -359,8 +359,8 @@ class WinIisTestCase(TestCase, LoaderModuleMockMixin):
'''
key = '*:443:mytestsite.local'
with patch.dict(win_iis.__salt__), \
patch('salt.modules.win_iis.list_sites',
MagicMock(return_value=SITE_LIST)):
patch('salt.modules.win_iis.list_sites',
MagicMock(return_value=SITE_LIST)):
self.assertEqual(win_iis.list_cert_bindings('MyTestSite'),
{key: BINDING_LIST[key]})
@ -372,10 +372,10 @@ class WinIisTestCase(TestCase, LoaderModuleMockMixin):
'site': 'MyOtherTestSite', 'hostheader': 'myothertestsite.local',
'ipaddress': '*', 'port': 443}
with patch.dict(win_iis.__salt__), \
patch('salt.modules.win_iis._srvmgr',
MagicMock(return_value={'retcode': 0})), \
patch('salt.modules.win_iis.list_cert_bindings',
MagicMock(return_value={CERT_BINDING_INFO: BINDING_LIST[CERT_BINDING_INFO]})):
patch('salt.modules.win_iis._srvmgr',
MagicMock(return_value={'retcode': 0})), \
patch('salt.modules.win_iis.list_cert_bindings',
MagicMock(return_value={CERT_BINDING_INFO: BINDING_LIST[CERT_BINDING_INFO]})):
self.assertTrue(win_iis.remove_cert_binding(**kwargs))
def test_get_container_setting(self):
@ -385,8 +385,8 @@ class WinIisTestCase(TestCase, LoaderModuleMockMixin):
kwargs = {'name': 'MyTestSite', 'container': 'AppPools',
'settings': ['managedPipelineMode']}
with patch.dict(win_iis.__salt__), \
patch('salt.modules.win_iis._srvmgr',
MagicMock(return_value=CONTAINER_SETTING)):
patch('salt.modules.win_iis._srvmgr',
MagicMock(return_value=CONTAINER_SETTING)):
self.assertEqual(win_iis.get_container_setting(**kwargs),
{'managedPipelineMode': 'Integrated'})
@ -397,8 +397,159 @@ class WinIisTestCase(TestCase, LoaderModuleMockMixin):
kwargs = {'name': 'MyTestSite', 'container': 'AppPools',
'settings': {'managedPipelineMode': 'Integrated'}}
with patch.dict(win_iis.__salt__), \
patch('salt.modules.win_iis._srvmgr',
MagicMock(return_value={'retcode': 0})), \
patch('salt.modules.win_iis.get_container_setting',
MagicMock(return_value={'managedPipelineMode': 'Integrated'})):
patch('salt.modules.win_iis._srvmgr',
MagicMock(return_value={'retcode': 0})), \
patch('salt.modules.win_iis.get_container_setting',
MagicMock(return_value={'managedPipelineMode': 'Integrated'})):
self.assertTrue(win_iis.set_container_setting(**kwargs))
def test__collection_match_to_index(self):
bad_match = {'key_0': 'value'}
first_match = {'key_1': 'value'}
second_match = {'key_2': 'value'}
collection = [first_match, second_match]
settings = [{'name': 'enabled', 'value': collection}]
with patch.dict(win_iis.__salt__), \
patch('salt.modules.win_iis.get_webconfiguration_settings',
MagicMock(return_value=settings)):
ret = win_iis._collection_match_to_index('pspath', 'colfilter', 'name', bad_match)
self.assertEqual(ret, -1)
ret = win_iis._collection_match_to_index('pspath', 'colfilter', 'name', first_match)
self.assertEqual(ret, 0)
ret = win_iis._collection_match_to_index('pspath', 'colfilter', 'name', second_match)
self.assertEqual(ret, 1)
def test__prepare_settings(self):
simple_setting = {'name': 'value', 'filter': 'value'}
collection_setting = {'name': 'Collection[{yaml:\n\tdata}]', 'filter': 'value'}
with patch.dict(win_iis.__salt__), \
patch('salt.modules.win_iis._collection_match_to_index',
MagicMock(return_value=0)):
ret = win_iis._prepare_settings('pspath', [
simple_setting, collection_setting, {'invalid': 'setting'}, {'name': 'filter-less_setting'}
])
self.assertEqual(ret, [simple_setting, collection_setting])
@patch('salt.modules.win_iis.log')
def test_get_webconfiguration_settings_empty(self, mock_log):
ret = win_iis.get_webconfiguration_settings('name', settings=[])
mock_log.warning.assert_called_once_with('No settings provided')
self.assertEqual(ret, {})
def test_get_webconfiguration_settings(self):
# Setup
name = 'IIS'
collection_setting = {'name': 'Collection[{yaml:\n\tdata}]', 'filter': 'value'}
filter_setting = {'name': 'enabled',
'filter': 'system.webServer / security / authentication / anonymousAuthentication'}
settings = [collection_setting, filter_setting]
ps_cmd = ['$Settings = New-Object System.Collections.ArrayList;', ]
for setting in settings:
ps_cmd.extend([
"$Property = Get-WebConfigurationProperty -PSPath '{}'".format(name),
"-Name '{name}' -Filter '{filter}' -ErrorAction Stop;".format(
filter=setting['filter'], name=setting['name']),
'if (([String]::IsNullOrEmpty($Property) -eq $False) -and',
"($Property.GetType()).Name -eq 'ConfigurationAttribute') {",
'$Property = $Property | Select-Object',
'-ExpandProperty Value };',
"$Settings.add(@{{filter='{filter}';name='{name}';value=[String] $Property}})| Out-Null;".format(
filter=setting['filter'], name=setting['name']),
'$Property = $Null;',
])
ps_cmd.append('$Settings')
# Execute
with patch.dict(win_iis.__salt__), \
patch('salt.modules.win_iis._prepare_settings',
MagicMock(return_value=settings)), \
patch('salt.modules.win_iis._srvmgr',
MagicMock(return_value={'retcode': 0, 'stdout': '{}'})):
ret = win_iis.get_webconfiguration_settings(name, settings=settings)
# Verify
win_iis._srvmgr.assert_called_with(cmd=ps_cmd, return_json=True)
self.assertEqual(ret, {})
@patch('salt.modules.win_iis.log')
def test_set_webconfiguration_settings_empty(self, mock_log):
ret = win_iis.set_webconfiguration_settings('name', settings=[])
mock_log.warning.assert_called_once_with('No settings provided')
self.assertEqual(ret, False)
@patch('salt.modules.win_iis.log')
def test_set_webconfiguration_settings_no_changes(self, mock_log):
# Setup
name = 'IIS'
setting = {
'name': 'Collection[{yaml:\n\tdata}]',
'filter': 'system.webServer / security / authentication / anonymousAuthentication',
'value': []
}
settings = [setting]
# Execute
with patch.dict(win_iis.__salt__), \
patch('salt.modules.win_iis._prepare_settings',
MagicMock(return_value=settings)), \
patch('salt.modules.win_iis._srvmgr',
MagicMock(return_value={'retcode': 0, 'stdout': '{}'})), \
patch('salt.modules.win_iis.get_webconfiguration_settings',
MagicMock(return_value=settings)):
ret = win_iis.set_webconfiguration_settings(name, settings=settings)
# Verify
mock_log.debug.assert_called_with('Settings already contain the provided values.')
self.assertEqual(ret, True)
@patch('salt.modules.win_iis.log')
def test_set_webconfiguration_settings_failed(self, mock_log):
# Setup
name = 'IIS'
setting = {
'name': 'Collection[{yaml:\n\tdata}]',
'filter': 'system.webServer / security / authentication / anonymousAuthentication',
'value': []
}
settings = [setting]
# Execute
with patch.dict(win_iis.__salt__), \
patch('salt.modules.win_iis._prepare_settings',
MagicMock(return_value=settings)), \
patch('salt.modules.win_iis._srvmgr',
MagicMock(return_value={'retcode': 0, 'stdout': '{}'})), \
patch('salt.modules.win_iis.get_webconfiguration_settings',
MagicMock(side_effect=[[], [{'value': 'unexpected_change!'}]])):
ret = win_iis.set_webconfiguration_settings(name, settings=settings)
# Verify
self.assertEqual(ret, False)
mock_log.error.assert_called_with('Failed to change settings: %s', settings)
@patch('salt.modules.win_iis.log')
def test_set_webconfiguration_settings(self, mock_log):
# Setup
name = 'IIS'
setting = {
'name': 'Collection[{yaml:\n\tdata}]',
'filter': 'system.webServer / security / authentication / anonymousAuthentication',
'value': []
}
settings = [setting]
# Execute
with patch.dict(win_iis.__salt__), \
patch('salt.modules.win_iis._prepare_settings',
MagicMock(return_value=settings)), \
patch('salt.modules.win_iis._srvmgr',
MagicMock(return_value={'retcode': 0, 'stdout': '{}'})), \
patch('salt.modules.win_iis.get_webconfiguration_settings',
MagicMock(side_effect=[[], settings])):
ret = win_iis.set_webconfiguration_settings(name, settings=settings)
# Verify
self.assertEqual(ret, True)
mock_log.debug.assert_called_with('Settings configured successfully: %s', settings)

View File

@ -0,0 +1,114 @@
# -*- coding: utf-8 -*-
'''
:synopsis: Unit Tests for Windows iis Module 'state.win_iis'
:platform: Windows
.. versionadded:: 2019.2.2
'''
# Import Python Libs
from __future__ import absolute_import, unicode_literals, print_function
# Import Salt Libs
import salt.states.win_iis as win_iis
# Import Salt Testing Libs
from tests.support.mixins import LoaderModuleMockMixin
from tests.support.unit import TestCase, skipIf
from tests.support.mock import (
MagicMock,
patch,
NO_MOCK,
NO_MOCK_REASON,
)
@skipIf(NO_MOCK, NO_MOCK_REASON)
class WinIisTestCase(TestCase, LoaderModuleMockMixin):
'''
Test cases for salt.states.win_pki
'''
def setup_loader_modules(self):
return {win_iis: {}}
def __base_webconfiguration_ret(self, comment='', changes=None, name='', result=None):
return {
'name': name,
'changes': changes if changes else {},
'comment': comment,
'result': result,
}
def test_webconfiguration_settings_no_settings(self):
name = 'IIS'
settings = {}
expected_ret = self.__base_webconfiguration_ret(name=name, comment='No settings to change provided.',
result=True)
actual_ret = win_iis.webconfiguration_settings(name, settings)
self.assertEqual(expected_ret, actual_ret)
def test_webconfiguration_settings_collection_failure(self):
name = 'IIS:\\'
settings = {
'system.applicationHost/sites': {
'Collection[{name: site0}].logFile.directory': 'C:\\logs\\iis\\site0',
},
}
old_settings = [
{'filter': 'system.applicationHost/sites', 'name': 'Collection[{name: site0}].logFile.directory',
'value': 'C:\\logs\\iis\\old_site'}]
current_settings = old_settings
new_settings = old_settings
expected_ret = self.__base_webconfiguration_ret(
name=name,
result=False,
changes={
'changes': {old_settings[0]['filter'] + '.' + old_settings[0]['name']: {
'old': old_settings[0]['value'],
'new': settings[old_settings[0]['filter']][old_settings[0]['name']],
}},
'failures': {old_settings[0]['filter'] + '.' + old_settings[0]['name']: {
'old': old_settings[0]['value'],
'new': new_settings[0]['value'],
}},
},
comment='Some settings failed to change.'
)
with patch.dict(win_iis.__salt__, {
'win_iis.get_webconfiguration_settings': MagicMock(
side_effect=[old_settings, current_settings, new_settings]),
'win_iis.set_webconfiguration_settings': MagicMock(return_value=True),
}), patch.dict(win_iis.__opts__, {'test': False}):
actual_ret = win_iis.webconfiguration_settings(name, settings)
self.assertEqual(expected_ret, actual_ret)
def test_webconfiguration_settings_collection(self):
name = 'IIS:\\'
settings = {
'system.applicationHost/sites': {
'Collection[{name: site0}].logFile.directory': 'C:\\logs\\iis\\site0',
},
}
old_settings = [
{'filter': 'system.applicationHost/sites', 'name': 'Collection[{name: site0}].logFile.directory',
'value': 'C:\\logs\\iis\\old_site'}]
current_settings = [
{'filter': 'system.applicationHost/sites', 'name': 'Collection[{name: site0}].logFile.directory',
'value': 'C:\\logs\\iis\\site0'}]
new_settings = current_settings
expected_ret = self.__base_webconfiguration_ret(
name=name,
result=True,
changes={old_settings[0]['filter'] + '.' + old_settings[0]['name']: {
'old': old_settings[0]['value'],
'new': new_settings[0]['value'],
}},
comment='Set settings to contain the provided values.'
)
with patch.dict(win_iis.__salt__, {
'win_iis.get_webconfiguration_settings': MagicMock(
side_effect=[old_settings, current_settings, new_settings]),
'win_iis.set_webconfiguration_settings': MagicMock(return_value=True),
}), patch.dict(win_iis.__opts__, {'test': False}):
actual_ret = win_iis.webconfiguration_settings(name, settings)
self.assertEqual(expected_ret, actual_ret)