Merge pull request #47533 from twangboy/fix_47178

Fix issues with functions that use makedirs on Windows
This commit is contained in:
Nicole Thomas 2018-06-06 13:20:50 -04:00 committed by GitHub
commit d56ddad22c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -662,13 +662,13 @@ def _error(ret, err_msg):
def _check_directory(name,
user,
group,
recurse,
mode,
clean,
require,
exclude_pat,
user=None,
group=None,
recurse=False,
mode=None,
clean=False,
require=False,
exclude_pat=None,
max_depth=None,
follow_symlinks=False):
'''
@ -753,7 +753,7 @@ def _check_directory(name,
def _check_directory_win(name,
win_owner,
win_owner=None,
win_perms=None,
win_deny_perms=None,
win_inheritance=None):
@ -766,6 +766,7 @@ def _check_directory_win(name,
changes = {name: {'directory': 'new'}}
else:
# Check owner
if win_owner is not None:
owner = salt.utils.win_dacl.get_owner(name)
if not owner.lower() == win_owner.lower():
changes['owner'] = win_owner
@ -930,33 +931,46 @@ def _check_touch(name, atime, mtime):
def _get_symlink_ownership(path):
if salt.utils.is_windows():
owner = salt.utils.win_dacl.get_owner(path)
return owner, owner
else:
return (
__salt__['file.get_user'](path, follow_symlinks=False),
__salt__['file.get_group'](path, follow_symlinks=False)
)
def _check_symlink_ownership(path, user, group):
def _check_symlink_ownership(path, user, group, win_owner):
'''
Check if the symlink ownership matches the specified user and group
'''
cur_user, cur_group = _get_symlink_ownership(path)
if salt.utils.is_windows():
return win_owner == cur_user
else:
return (cur_user == user) and (cur_group == group)
def _set_symlink_ownership(path, user, group):
def _set_symlink_ownership(path, user, group, win_owner):
'''
Set the ownership of a symlink and return a boolean indicating
success/failure
'''
if salt.utils.is_windows():
try:
salt.utils.win_dacl.set_owner(path, win_owner)
except CommandExecutionError:
pass
else:
try:
__salt__['file.lchown'](path, user, group)
except OSError:
pass
return _check_symlink_ownership(path, user, group)
return _check_symlink_ownership(path, user, group, win_owner)
def _symlink_check(name, target, force, user, group):
def _symlink_check(name, target, force, user, group, win_owner):
'''
Check the symlink function
'''
@ -975,7 +989,7 @@ def _symlink_check(name, target, force, user, group):
else:
result = True
msg = 'The symlink {0} is present'.format(name)
if not _check_symlink_ownership(name, user, group):
if not _check_symlink_ownership(name, user, group, win_owner):
result = None
pchanges['ownership'] = '{0}:{1}'.format(*_get_symlink_ownership(name))
msg += (
@ -1199,6 +1213,57 @@ def _shortcut_check(name,
'should be. Did you mean to use force?'.format(name)), pchanges
def _makedirs(name,
user=None,
group=None,
dir_mode=None,
win_owner=None,
win_perms=None,
win_deny_perms=None,
win_inheritance=None):
'''
Helper function for creating directories when the ``makedirs`` option is set
to ``True``. Handles Unix and Windows based systems
.. versionadded:: 2017.7.7
Args:
name (str): The directory path to create
user (str): The linux user to own the directory
group (str): The linux group to own the directory
dir_mode (str): The linux mode to apply to the directory
win_owner (str): The Windows user to own the directory
win_perms (dict): A dictionary of grant permissions for Windows
win_deny_perms (dict): A dictionary of deny permissions for Windows
win_inheritance (bool): True to inherit permissions on Windows
Returns:
bool: True if successful, otherwise False on Windows
str: Error messages on failure on Linux
None: On successful creation on Linux
Raises:
CommandExecutionError: If the drive is not mounted on Windows
'''
if salt.utils.is_windows():
# Make sure the drive is mapped before trying to create the
# path in windows
drive, path = os.path.splitdrive(name)
if not os.path.isdir(drive):
raise CommandExecutionError(drive)
win_owner = win_owner if win_owner else user
return __salt__['file.makedirs'](path=name,
owner=win_owner,
grant_perms=win_perms,
deny_perms=win_deny_perms,
inheritance=win_inheritance)
else:
return __salt__['file.makedirs'](path=name,
user=user,
group=group,
mode=dir_mode)
def symlink(
name,
target,
@ -1208,6 +1273,10 @@ def symlink(
user=None,
group=None,
mode=None,
win_owner=None,
win_perms=None,
win_deny_perms=None,
win_inheritance=None,
**kwargs):
'''
Create a symbolic link (symlink, soft link)
@ -1256,6 +1325,28 @@ def symlink(
The default mode for new files and directories corresponds umask of salt
process. For existing files and directories it's not enforced.
win_owner : None
The owner of the symlink and directories if ``makedirs`` is True. If
this is not passed, ``user`` will be used. If ``user`` is not passed,
the account under which Salt is running will be used.
.. versionadded:: 2017.7.7
win_perms : None
A dictionary containing permissions to grant
.. versionadded:: 2017.7.7
win_deny_perms : None
A dictionary containing permissions to deny
.. versionadded:: 2017.7.7
win_inheritance : None
True to inherit permissions from parent, otherwise False
.. versionadded:: 2017.7.7
'''
name = os.path.expanduser(name)
@ -1284,10 +1375,16 @@ def symlink(
if not user:
user = 'SYSTEM'
# If win_owner is not passed, use user
if win_owner is None:
win_owner = user if user else None
# Group isn't relevant to Windows, use win_perms/win_deny_perms
if group is not None:
log.warning(
'The group argument for {0} has been ignored as this '
'is a Windows system.'.format(name)
'is a Windows system. Please use the `win_*` parameters to set '
'permissions in Windows.'.format(name)
)
group = user
@ -1297,6 +1394,23 @@ def symlink(
)
preflight_errors = []
if salt.utils.is_windows():
# Make sure the passed owner exists
if not salt.utils.win_functions.get_sid_from_name(win_owner):
preflight_errors.append('User {0} does not exist'.format(win_owner))
# Make sure users passed in win_perms exist
if win_perms:
for name_check in win_perms:
if not salt.utils.win_functions.get_sid_from_name(name_check):
preflight_errors.append('User {0} does not exist'.format(name_check))
# Make sure users passed in win_deny_perms exist
if win_deny_perms:
for name_check in win_deny_perms:
if not salt.utils.win_functions.get_sid_from_name(name_check):
preflight_errors.append('User {0} does not exist'.format(name_check))
else:
uid = __salt__['file.user_to_uid'](user)
gid = __salt__['file.group_to_gid'](group)
@ -1321,19 +1435,29 @@ def symlink(
target,
force,
user,
group)
if __opts__['test']:
ret['result'] = presult
ret['comment'] = pcomment
return ret
group,
win_owner)
if not os.path.isdir(os.path.dirname(name)):
if makedirs:
__salt__['file.makedirs'](
name,
if __opts__['test']:
pcomment += '\n{0} will be created'.format(os.path.dirname(name))
else:
try:
_makedirs(name=name,
user=user,
group=group,
mode=mode)
dir_mode=mode,
win_owner=win_owner,
win_perms=win_perms,
win_deny_perms=win_deny_perms,
win_inheritance=win_inheritance)
except CommandExecutionError as exc:
return _error(ret, 'Drive {0} is not mapped'.format(exc.message))
else:
if __opts__['test']:
pcomment += '\nDirectory {0} for symlink is not present' \
''.format(os.path.dirname(name))
else:
return _error(
ret,
@ -1341,27 +1465,47 @@ def symlink(
os.path.dirname(name)
)
)
if __opts__['test']:
ret['result'] = presult
ret['comment'] = pcomment
return ret
if __salt__['file.is_link'](name):
# The link exists, verify that it matches the target
if os.path.normpath(__salt__['file.readlink'](name)) != os.path.normpath(target):
# The target is wrong, delete the link
os.remove(name)
else:
if _check_symlink_ownership(name, user, group):
if _check_symlink_ownership(name, user, group, win_owner):
# The link looks good!
if salt.utils.is_windows():
ret['comment'] = ('Symlink {0} is present and owned by {1}'
''.format(name, win_owner))
else:
ret['comment'] = ('Symlink {0} is present and owned by '
'{1}:{2}'.format(name, user, group))
else:
if _set_symlink_ownership(name, user, group):
if _set_symlink_ownership(name, user, group, win_owner):
if salt.utils.is_windows():
ret['comment'] = ('Set ownership of symlink {0} to '
'{1}'.format(name, win_owner))
ret['changes']['ownership'] = win_owner
else:
ret['comment'] = ('Set ownership of symlink {0} to '
'{1}:{2}'.format(name, user, group))
ret['changes']['ownership'] = '{0}:{1}'.format(user, group)
ret['changes']['ownership'] = '{0}:{1}'.format(user,
group)
else:
ret['result'] = False
if salt.utils.is_windows():
ret['comment'] += (
'Failed to set ownership of symlink '
'{0} to {1}'.format(name, win_owner))
else:
ret['comment'] += (
'Failed to set ownership of symlink {0} to '
'{1}:{2}'.format(name, user, group)
)
'{1}:{2}'.format(name, user, group))
return ret
elif os.path.isfile(name) or os.path.isdir(name):
@ -1408,8 +1552,8 @@ def symlink(
'{1}'.format(name, target))
ret['changes']['new'] = name
if not _check_symlink_ownership(name, user, group):
if not _set_symlink_ownership(name, user, group):
if not _check_symlink_ownership(name, user, group, win_owner):
if not _set_symlink_ownership(name, user, group, win_owner):
ret['result'] = False
ret['comment'] += (', but was unable to set ownership to '
'{0}:{1}'.format(user, group))
@ -2881,18 +3025,17 @@ def directory(name,
# The parent directory does not exist, create them
if makedirs:
# Everything's good, create the parent Dirs
if salt.utils.is_windows():
# Make sure the drive is mapped before trying to create the
# path in windows
drive, path = os.path.splitdrive(name)
if not os.path.isdir(drive):
return _error(
ret, 'Drive {0} is not mapped'.format(drive))
__salt__['file.makedirs'](name, win_owner, win_perms,
win_deny_perms, win_inheritance)
else:
__salt__['file.makedirs'](name, user=user, group=group,
mode=dir_mode)
try:
_makedirs(name=name,
user=user,
group=group,
dir_mode=dir_mode,
win_owner=win_owner,
win_perms=win_perms,
win_deny_perms=win_deny_perms,
win_inheritance=win_inheritance)
except CommandExecutionError as exc:
return _error(ret, 'Drive {0} is not mapped'.format(exc.message))
else:
return _error(
ret, 'No directory to create {0} in'.format(name))
@ -3064,6 +3207,10 @@ def recurse(name,
maxdepth=None,
keep_symlinks=False,
force_symlinks=False,
win_owner=None,
win_perms=None,
win_deny_perms=None,
win_inheritance=True,
**kwargs):
'''
Recurse through a subdirectory on the master and copy said subdirectory
@ -3218,6 +3365,28 @@ def recurse(name,
If a file or directory is obstructing symlink creation it will be
recursively removed so that symlink creation can proceed. This
option is usually not needed except in special circumstances.
win_owner : None
The owner of the symlink and directories if ``makedirs`` is True. If
this is not passed, ``user`` will be used. If ``user`` is not passed,
the account under which Salt is running will be used.
.. versionadded:: 2017.7.7
win_perms : None
A dictionary containing permissions to grant
.. versionadded:: 2017.7.7
win_deny_perms : None
A dictionary containing permissions to deny
.. versionadded:: 2017.7.7
win_inheritance : None
True to inherit permissions from parent, otherwise False
.. versionadded:: 2017.7.7
'''
if 'env' in kwargs:
salt.utils.warn_until(
@ -3321,7 +3490,18 @@ def recurse(name,
return _error(
ret, 'The path {0} exists and is not a directory'.format(name))
if not __opts__['test']:
__salt__['file.makedirs_perms'](name, user, group, dir_mode)
if salt.utils.is_windows():
win_owner = win_owner if win_owner else user
__salt__['file.makedirs_perms'](path=name,
owner=win_owner,
grant_perms=win_perms,
deny_perms=win_deny_perms,
inheritance=win_inheritance)
else:
__salt__['file.makedirs_perms'](name=name,
user=user,
group=group,
mode=dir_mode)
def add_comment(path, comment):
comments = ret['comment'].setdefault(path, [])
@ -4701,10 +4881,16 @@ def append(name,
if makedirs is True:
dirname = os.path.dirname(name)
if not __salt__['file.directory_exists'](dirname):
__salt__['file.makedirs'](name)
check_res, check_msg, ret['pchanges'] = _check_directory(
dirname, None, None, False, None, False, False, None
)
try:
_makedirs(name=name)
except CommandExecutionError as exc:
return _error(ret, 'Drive {0} is not mapped'.format(exc.message))
if salt.utils.is_windows():
check_res, check_msg, ret['pchanges'] = _check_directory_win(dirname)
else:
check_res, check_msg, ret['pchanges'] = _check_directory(dirname)
if not check_res:
return _error(ret, check_msg)
@ -4815,6 +5001,90 @@ def prepend(name,
The text will not be prepended again if it already exists in the file. You
may specify a single line of text or a list of lines to append.
name
The location of the file to append to.
text
The text to be appended, which can be a single string or a list
of strings.
makedirs
If the file is located in a path without a parent directory,
then the state will fail. If makedirs is set to True, then
the parent directories will be created to facilitate the
creation of the named file. Defaults to False.
source
A single source file to append. This source file can be hosted on either
the salt master server, or on an HTTP or FTP server. Both HTTPS and
HTTP are supported as well as downloading directly from Amazon S3
compatible URLs with both pre-configured and automatic IAM credentials
(see s3.get state documentation). File retrieval from Openstack Swift
object storage is supported via swift://container/object_path URLs
(see swift.get documentation).
For files hosted on the salt file server, if the file is located on
the master in the directory named spam, and is called eggs, the source
string is salt://spam/eggs.
If the file is hosted on an HTTP or FTP server, the source_hash argument
is also required.
source_hash
This can be one of the following:
1. a source hash string
2. the URI of a file that contains source hash strings
The function accepts the first encountered long unbroken alphanumeric
string of correct length as a valid hash, in order from most secure to
least secure:
.. code-block:: text
Type Length
====== ======
sha512 128
sha384 96
sha256 64
sha224 56
sha1 40
md5 32
See the ``source_hash`` parameter description for :mod:`file.managed
<salt.states.file.managed>` function for more details and examples.
template
The named templating engine will be used to render the appended-to file.
Defaults to ``jinja``. The following templates are supported:
- :mod:`cheetah<salt.renderers.cheetah>`
- :mod:`genshi<salt.renderers.genshi>`
- :mod:`jinja<salt.renderers.jinja>`
- :mod:`mako<salt.renderers.mako>`
- :mod:`py<salt.renderers.py>`
- :mod:`wempy<salt.renderers.wempy>`
sources
A list of source files to append. If the files are hosted on an HTTP or
FTP server, the source_hashes argument is also required.
source_hashes
A list of source_hashes corresponding to the sources list specified in
the sources argument.
defaults
Default context passed to the template.
context
Overrides default context variables passed to the template.
ignore_whitespace
.. versionadded:: 2015.8.4
Spaces and Tabs in text are ignored by default, when searching for the
appending content, one space or multiple tabs are the same for salt.
Set this option to ``False`` if you want to change this behavior.
Multi-line example:
.. code-block:: yaml
@ -4893,10 +5163,15 @@ def prepend(name,
if makedirs is True:
dirname = os.path.dirname(name)
if not __salt__['file.directory_exists'](dirname):
__salt__['file.makedirs'](name)
check_res, check_msg, ret['pchanges'] = _check_directory(
dirname, None, None, False, None, False, False, None
)
try:
_makedirs(name=name)
except CommandExecutionError as exc:
return _error(ret, 'Drive {0} is not mapped'.format(exc.message))
if salt.utils.is_windows():
check_res, check_msg, ret['pchanges'] = _check_directory_win(dirname)
else:
check_res, check_msg, ret['pchanges'] = _check_directory(dirname)
if not check_res:
return _error(ret, check_msg)
@ -5187,7 +5462,10 @@ def touch(name, atime=None, mtime=None, makedirs=False):
return ret
if makedirs:
__salt__['file.makedirs'](name)
try:
_makedirs(name=name)
except CommandExecutionError as exc:
return _error(ret, 'Drive {0} is not mapped'.format(exc.message))
if not os.path.isdir(os.path.dirname(name)):
return _error(
ret, 'Directory not present to touch file {0}'.format(name)
@ -5384,7 +5662,10 @@ def copy(
dname = os.path.dirname(name)
if not os.path.isdir(dname):
if makedirs:
__salt__['file.makedirs'](name)
try:
_makedirs(name=name)
except CommandExecutionError as exc:
return _error(ret, 'Drive {0} is not mapped'.format(exc.message))
else:
return _error(
ret,
@ -5481,7 +5762,10 @@ def rename(name, source, force=False, makedirs=False):
dname = os.path.dirname(name)
if not os.path.isdir(dname):
if makedirs:
__salt__['file.makedirs'](name)
try:
_makedirs(name=name)
except CommandExecutionError as exc:
return _error(ret, 'Drive {0} is not mapped'.format(exc.message))
else:
return _error(
ret,
@ -6377,9 +6661,10 @@ def shortcut(
if not os.path.isdir(os.path.dirname(name)):
if makedirs:
__salt__['file.makedirs'](
name,
user=user)
try:
_makedirs(name=name, user=user)
except CommandExecutionError as exc:
return _error(ret, 'Drive {0} is not mapped'.format(exc.message))
else:
return _error(
ret,
@ -6402,7 +6687,10 @@ def shortcut(
time.sleep(1) # wait for asynchronous deletion
if not os.path.isdir(os.path.dirname(backupname)):
if makedirs:
os.makedirs(backupname)
try:
_makedirs(name=backupname)
except CommandExecutionError as exc:
return _error(ret, 'Drive {0} is not mapped'.format(exc.message))
else:
return _error(ret, (
'Directory does not exist for'