mirror of
https://github.com/valitydev/salt.git
synced 2024-11-07 08:58:59 +00:00
Merge pull request #46316 from twangboy/win_fix_dsc
Fix issues with the DSC module
This commit is contained in:
commit
decccbeca3
@ -39,33 +39,35 @@ def __virtual__():
|
||||
'''
|
||||
# Verify Windows
|
||||
if not salt.utils.is_windows():
|
||||
log.debug('Module DSC: Only available on Windows systems')
|
||||
return False, 'Module DSC: Only available on Windows systems'
|
||||
log.debug('DSC: Only available on Windows systems')
|
||||
return False, 'DSC: Only available on Windows systems'
|
||||
|
||||
# Verify PowerShell
|
||||
powershell_info = __salt__['cmd.shell_info']('powershell')
|
||||
if not powershell_info['installed']:
|
||||
log.debug('Module DSC: Requires PowerShell')
|
||||
return False, 'Module DSC: Requires PowerShell'
|
||||
log.debug('DSC: Requires PowerShell')
|
||||
return False, 'DSC: Requires PowerShell'
|
||||
|
||||
# Verify PowerShell 5.0 or greater
|
||||
if salt.utils.compare_versions(powershell_info['version'], '<', '5.0'):
|
||||
log.debug('Module DSC: Requires PowerShell 5 or later')
|
||||
return False, 'Module DSC: Requires PowerShell 5 or later'
|
||||
log.debug('DSC: Requires PowerShell 5 or later')
|
||||
return False, 'DSC: Requires PowerShell 5 or later'
|
||||
|
||||
return __virtualname__
|
||||
|
||||
|
||||
def _pshell(cmd, cwd=None, json_depth=2):
|
||||
def _pshell(cmd, cwd=None, json_depth=2, ignore_retcode=False):
|
||||
'''
|
||||
Execute the desired PowerShell command and ensure that it returns data
|
||||
in json format and load that into python
|
||||
in json format and load that into python. Either return a dict or raise a
|
||||
CommandExecutionError.
|
||||
'''
|
||||
if 'convertto-json' not in cmd.lower():
|
||||
cmd = '{0} | ConvertTo-Json -Depth {1}'.format(cmd, json_depth)
|
||||
log.debug('DSC: {0}'.format(cmd))
|
||||
results = __salt__['cmd.run_all'](
|
||||
cmd, shell='powershell', cwd=cwd, python_shell=True)
|
||||
cmd, shell='powershell', cwd=cwd, python_shell=True,
|
||||
ignore_retcode=ignore_retcode)
|
||||
|
||||
if 'pid' in results:
|
||||
del results['pid']
|
||||
@ -75,12 +77,17 @@ def _pshell(cmd, cwd=None, json_depth=2):
|
||||
raise CommandExecutionError(
|
||||
'Issue executing PowerShell {0}'.format(cmd), info=results)
|
||||
|
||||
# Sometimes Powershell returns an empty string, which isn't valid JSON
|
||||
if results['stdout'] == '':
|
||||
results['stdout'] = '{}'
|
||||
|
||||
try:
|
||||
ret = json.loads(results['stdout'], strict=False)
|
||||
except ValueError:
|
||||
raise CommandExecutionError(
|
||||
'No JSON results from PowerShell', info=results)
|
||||
|
||||
log.info('DSC: Returning "{0}"'.format(ret))
|
||||
return ret
|
||||
|
||||
|
||||
@ -98,8 +105,8 @@ def run_config(path,
|
||||
script, the desired configuration can be applied by passing the name in the
|
||||
``config`` option.
|
||||
|
||||
This command would be the equivalent of running ``dsc.compile_config`` and
|
||||
``dsc.apply_config`` separately.
|
||||
This command would be the equivalent of running ``dsc.compile_config``
|
||||
followed by ``dsc.apply_config``.
|
||||
|
||||
Args:
|
||||
|
||||
@ -141,7 +148,7 @@ def run_config(path,
|
||||
Default is 'base'
|
||||
|
||||
Returns:
|
||||
bool: True if successfully compiled and applied, False if not
|
||||
bool: True if successfully compiled and applied, otherwise False
|
||||
|
||||
CLI Example:
|
||||
|
||||
@ -149,13 +156,13 @@ def run_config(path,
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' dsc.compile_apply_config C:\\DSC\\WebsiteConfig.ps1
|
||||
salt '*' dsc.run_config C:\\DSC\\WebsiteConfig.ps1
|
||||
|
||||
To cache a config script to the system from the master and compile it:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' dsc.compile_apply_config C:\\DSC\\WebsiteConfig.ps1 salt://dsc/configs/WebsiteConfig.ps1
|
||||
salt '*' dsc.run_config C:\\DSC\\WebsiteConfig.ps1 salt://dsc/configs/WebsiteConfig.ps1
|
||||
'''
|
||||
ret = compile_config(path=path,
|
||||
source=source,
|
||||
@ -240,31 +247,31 @@ def compile_config(path,
|
||||
salt '*' dsc.compile_config C:\\DSC\\WebsiteConfig.ps1 salt://dsc/configs/WebsiteConfig.ps1
|
||||
'''
|
||||
if source:
|
||||
log.info('Caching {0}'.format(source))
|
||||
log.info('DSC: Caching {0}'.format(source))
|
||||
cached_files = __salt__['cp.get_file'](path=source,
|
||||
dest=path,
|
||||
saltenv=salt_env,
|
||||
makedirs=True)
|
||||
if not cached_files:
|
||||
error = 'Failed to cache {0}'.format(source)
|
||||
log.error(error)
|
||||
log.error('DSC: {0}'.format(error))
|
||||
raise CommandExecutionError(error)
|
||||
|
||||
if config_data_source:
|
||||
log.info('Caching {0}'.format(config_data_source))
|
||||
log.info('DSC: Caching {0}'.format(config_data_source))
|
||||
cached_files = __salt__['cp.get_file'](path=config_data_source,
|
||||
dest=config_data,
|
||||
saltenv=salt_env,
|
||||
makedirs=True)
|
||||
if not cached_files:
|
||||
error = 'Failed to cache {0}'.format(config_data_source)
|
||||
log.error(error)
|
||||
log.error('DSC: {0}'.format(error))
|
||||
raise CommandExecutionError(error)
|
||||
|
||||
# Make sure the path exists
|
||||
if not os.path.exists(path):
|
||||
error = '"{0} not found.'.format(path)
|
||||
log.error(error)
|
||||
error = '"{0}" not found'.format(path)
|
||||
log.error('DSC: {0}'.format(error))
|
||||
raise CommandExecutionError(error)
|
||||
|
||||
if config_name is None:
|
||||
@ -290,10 +297,11 @@ def compile_config(path,
|
||||
if ret:
|
||||
# Script compiled, return results
|
||||
if ret.get('Exists'):
|
||||
log.info('DSC Compile Config: {0}'.format(ret))
|
||||
log.info('DSC: Compile Config: {0}'.format(ret))
|
||||
return ret
|
||||
|
||||
# Run the script and run the compile command
|
||||
# If you get to this point, the script did not contain a compile command
|
||||
# dot source the script to compile the state and generate the mof file
|
||||
cmd = ['.', path]
|
||||
if script_parameters:
|
||||
cmd.append(script_parameters)
|
||||
@ -311,12 +319,12 @@ def compile_config(path,
|
||||
if ret:
|
||||
# Script compiled, return results
|
||||
if ret.get('Exists'):
|
||||
log.info('DSC Compile Config: {0}'.format(ret))
|
||||
log.info('DSC: Compile Config: {0}'.format(ret))
|
||||
return ret
|
||||
|
||||
error = 'Failed to compile config: {0}'.format(path)
|
||||
error += '\nReturned: {0}'.format(ret)
|
||||
log.error('DSC Compile Config: {0}'.format(error))
|
||||
log.error('DSC: {0}'.format(error))
|
||||
raise CommandExecutionError(error)
|
||||
|
||||
|
||||
@ -348,13 +356,13 @@ def apply_config(path, source=None, salt_env='base'):
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' dsc.run_config C:\\DSC\\WebSiteConfiguration
|
||||
salt '*' dsc.apply_config C:\\DSC\\WebSiteConfiguration
|
||||
|
||||
To cache a configuration from the master and apply it:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' dsc.run_config C:\\DSC\\WebSiteConfiguration salt://dsc/configs/WebSiteConfiguration
|
||||
salt '*' dsc.apply_config C:\\DSC\\WebSiteConfiguration salt://dsc/configs/WebSiteConfiguration
|
||||
|
||||
'''
|
||||
# If you're getting an error along the lines of "The client cannot connect
|
||||
@ -368,38 +376,35 @@ def apply_config(path, source=None, salt_env='base'):
|
||||
if path_name.lower() != source_name.lower():
|
||||
# Append the Source name to the Path
|
||||
path = '{0}\\{1}'.format(path, source_name)
|
||||
log.debug('{0} appended to the path.'.format(source_name))
|
||||
log.debug('DSC: {0} appended to the path'.format(source_name))
|
||||
|
||||
# Destination path minus the basename
|
||||
dest_path = os.path.dirname(os.path.normpath(path))
|
||||
log.info('Caching {0}'.format(source))
|
||||
log.info('DSC: Caching {0}'.format(source))
|
||||
cached_files = __salt__['cp.get_dir'](source, dest_path, salt_env)
|
||||
if not cached_files:
|
||||
error = 'Failed to copy {0}'.format(source)
|
||||
log.error(error)
|
||||
log.error('DSC: {0}'.format(error))
|
||||
raise CommandExecutionError(error)
|
||||
else:
|
||||
config = os.path.dirname(cached_files[0])
|
||||
|
||||
# Make sure the path exists
|
||||
if not os.path.exists(config):
|
||||
error = '{0} not found.'.format(config)
|
||||
log.error(error)
|
||||
error = '{0} not found'.format(config)
|
||||
log.error('DSC: {0}'.format(error))
|
||||
raise CommandExecutionError(error)
|
||||
|
||||
# Run the DSC Configuration
|
||||
# Putting quotes around the parameter protects against command injection
|
||||
cmd = 'Start-DscConfiguration -Path "{0}" -Wait -Force'.format(config)
|
||||
ret = _pshell(cmd)
|
||||
|
||||
if ret is False:
|
||||
raise CommandExecutionError('Apply Config Failed: {0}'.format(path))
|
||||
_pshell(cmd)
|
||||
|
||||
cmd = '$status = Get-DscConfigurationStatus; $status.Status'
|
||||
ret = _pshell(cmd)
|
||||
log.info('DSC Apply Config: {0}'.format(ret))
|
||||
log.info('DSC: Apply Config: {0}'.format(ret))
|
||||
|
||||
return ret == 'Success'
|
||||
return ret == 'Success' or ret == {}
|
||||
|
||||
|
||||
def get_config():
|
||||
@ -409,15 +414,153 @@ def get_config():
|
||||
Returns:
|
||||
dict: A dictionary representing the DSC Configuration on the machine
|
||||
|
||||
Raises:
|
||||
CommandExecutionError: On failure
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' dsc.get_config
|
||||
'''
|
||||
cmd = 'Get-DscConfiguration | ' \
|
||||
'Select-Object * -ExcludeProperty Cim*'
|
||||
return _pshell(cmd)
|
||||
cmd = 'Get-DscConfiguration | Select-Object * -ExcludeProperty Cim*'
|
||||
|
||||
try:
|
||||
raw_config = _pshell(cmd, ignore_retcode=True)
|
||||
except CommandExecutionError as exc:
|
||||
if 'Current configuration does not exist' in exc.info['stderr']:
|
||||
raise CommandExecutionError('Not Configured')
|
||||
raise
|
||||
|
||||
config = dict()
|
||||
if raw_config:
|
||||
# Get DSC Configuration Name
|
||||
if 'ConfigurationName' in raw_config[0]:
|
||||
config[raw_config[0]['ConfigurationName']] = {}
|
||||
# Add all DSC Configurations by ResourceId
|
||||
for item in raw_config:
|
||||
config[item['ConfigurationName']][item['ResourceId']] = {}
|
||||
for key in item:
|
||||
if key not in ['ConfigurationName', 'ResourceId']:
|
||||
config[item['ConfigurationName']][item['ResourceId']][key] = item[key]
|
||||
|
||||
return config
|
||||
|
||||
|
||||
def remove_config(reset=False):
|
||||
'''
|
||||
Remove the current DSC Configuration. Removes current, pending, and previous
|
||||
dsc configurations.
|
||||
|
||||
.. versionadded:: 2017.7.5
|
||||
|
||||
Args:
|
||||
reset (bool):
|
||||
Attempts to reset the DSC configuration by removing the following
|
||||
from ``C:\\Windows\\System32\\Configuration``:
|
||||
|
||||
- File: DSCStatusHistory.mof
|
||||
- File: DSCEngineCache.mof
|
||||
- Dir: ConfigurationStatus
|
||||
|
||||
Default is False
|
||||
|
||||
.. warning::
|
||||
``remove_config`` may fail to reset the DSC environment if any
|
||||
of the files in the ``ConfigurationStatus`` directory. If you
|
||||
wait a few minutes and run again, it may complete successfully.
|
||||
|
||||
Returns:
|
||||
bool: True if successful
|
||||
|
||||
Raises:
|
||||
CommandExecutionError: On failure
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' dsc.remove_config True
|
||||
'''
|
||||
# Stopping a running config (not likely to occur)
|
||||
cmd = 'Stop-DscConfiguration'
|
||||
log.info('DSC: Stopping Running Configuration')
|
||||
try:
|
||||
_pshell(cmd)
|
||||
except CommandExecutionError as exc:
|
||||
if exc.info['retcode'] != 0:
|
||||
raise CommandExecutionError('Failed to Stop DSC Configuration',
|
||||
info=exc.info)
|
||||
log.info('DSC: {0}'.format(exc.info['stdout']))
|
||||
|
||||
# Remove configuration files
|
||||
cmd = 'Remove-DscConfigurationDocument -Stage Current, Pending, Previous ' \
|
||||
'-Force'
|
||||
log.info('DSC: Removing Configuration')
|
||||
try:
|
||||
_pshell(cmd)
|
||||
except CommandExecutionError as exc:
|
||||
if exc.info['retcode'] != 0:
|
||||
raise CommandExecutionError('Failed to remove DSC Configuration',
|
||||
info=exc.info)
|
||||
log.info('DSC: {0}'.format(exc.info['stdout']))
|
||||
|
||||
if not reset:
|
||||
return True
|
||||
|
||||
def _remove_fs_obj(path):
|
||||
if os.path.exists(path):
|
||||
log.info('DSC: Removing {0}'.format(path))
|
||||
if not __salt__['file.remove'](path):
|
||||
error = 'Failed to remove {0}'.format(path)
|
||||
log.error('DSC: {0}'.format(error))
|
||||
raise CommandExecutionError(error)
|
||||
|
||||
dsc_config_dir = '{0}\\System32\\Configuration' \
|
||||
''.format(os.getenv('SystemRoot', 'C:\\Windows'))
|
||||
|
||||
# Remove History
|
||||
_remove_fs_obj('{0}\\DSCStatusHistory.mof'.format(dsc_config_dir))
|
||||
|
||||
# Remove Engine Cache
|
||||
_remove_fs_obj('{0}\\DSCEngineCache.mof'.format(dsc_config_dir))
|
||||
|
||||
# Remove Status Directory
|
||||
_remove_fs_obj('{0}\\ConfigurationStatus'.format(dsc_config_dir))
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def restore_config():
|
||||
'''
|
||||
Reapplies the previous configuration.
|
||||
|
||||
.. versionadded:: 2017.7.5
|
||||
|
||||
.. note::
|
||||
The current configuration will be come the previous configuration. If
|
||||
run a second time back-to-back it is like toggling between two configs.
|
||||
|
||||
Returns:
|
||||
bool: True if successfully restored
|
||||
|
||||
Raises:
|
||||
CommandExecutionError: On failure
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt '*' dsc.restore_config
|
||||
'''
|
||||
cmd = 'Restore-DscConfiguration'
|
||||
try:
|
||||
_pshell(cmd, ignore_retcode=True)
|
||||
except CommandExecutionError as exc:
|
||||
if 'A previous configuration does not exist' in exc.info['stderr']:
|
||||
raise CommandExecutionError('Previous Configuration Not Found')
|
||||
raise
|
||||
return True
|
||||
|
||||
|
||||
def test_config():
|
||||
@ -433,9 +576,13 @@ def test_config():
|
||||
|
||||
salt '*' dsc.test_config
|
||||
'''
|
||||
cmd = 'Test-DscConfiguration *>&1'
|
||||
ret = _pshell(cmd)
|
||||
return ret == 'True'
|
||||
cmd = 'Test-DscConfiguration'
|
||||
try:
|
||||
_pshell(cmd, ignore_retcode=True)
|
||||
except CommandExecutionError as exc:
|
||||
if 'Current configuration does not exist' in exc.info['stderr']:
|
||||
raise CommandExecutionError('Not Configured')
|
||||
raise
|
||||
|
||||
|
||||
def get_config_status():
|
||||
@ -456,7 +603,12 @@ def get_config_status():
|
||||
'Select-Object -Property HostName, Status, MetaData, ' \
|
||||
'@{Name="StartDate";Expression={Get-Date ($_.StartDate) -Format g}}, ' \
|
||||
'Type, Mode, RebootRequested, NumberofResources'
|
||||
return _pshell(cmd)
|
||||
try:
|
||||
return _pshell(cmd, ignore_retcode=True)
|
||||
except CommandExecutionError as exc:
|
||||
if 'No status information available' in exc.info['stderr']:
|
||||
raise CommandExecutionError('Not Configured')
|
||||
raise
|
||||
|
||||
|
||||
def get_lcm_config():
|
||||
@ -638,8 +790,8 @@ def set_lcm_config(config_mode=None,
|
||||
ret = __salt__['cmd.run_all'](cmd, shell='powershell', python_shell=True)
|
||||
__salt__['file.remove'](r'{0}\SaltConfig'.format(temp_dir))
|
||||
if not ret['retcode']:
|
||||
log.info('LCM config applied successfully')
|
||||
log.info('DSC: LCM config applied successfully')
|
||||
return True
|
||||
else:
|
||||
log.error('Failed to apply LCM config. Error {0}'.format(ret))
|
||||
log.error('DSC: Failed to apply LCM config. Error {0}'.format(ret))
|
||||
return False
|
||||
|
Loading…
Reference in New Issue
Block a user