Merge branch '2016.11' into '2017.7'

Conflicts:
  - salt/modules/win_pkg.py
  - salt/states/file.py
  - tests/integration/states/test_file.py
  - tests/unit/states/test_file.py
This commit is contained in:
rallytime 2017-12-07 17:33:03 -05:00
commit 23c5a4ca3e
No known key found for this signature in database
GPG key ID: E8F1A4B90D0DEA19
12 changed files with 239 additions and 53 deletions

View file

@ -240,7 +240,7 @@ class SyncClientMixin(object):
def low(self, fun, low, print_event=True, full_return=False):
'''
Check for deprecated usage and allow until Salt Oxygen.
Check for deprecated usage and allow until Salt Fluorine.
'''
msg = []
if 'args' in low:
@ -251,7 +251,7 @@ class SyncClientMixin(object):
low['kwarg'] = low.pop('kwargs')
if msg:
salt.utils.warn_until('Oxygen', ' '.join(msg))
salt.utils.warn_until('Fluorine', ' '.join(msg))
return self._low(fun, low, print_event=print_event, full_return=full_return)

View file

@ -147,8 +147,24 @@ def _render_tab(lst):
cron['cmd']
)
)
for spec in lst['special']:
ret.append('{0} {1}\n'.format(spec['spec'], spec['cmd']))
for cron in lst['special']:
if cron['comment'] is not None or cron['identifier'] is not None:
comment = '#'
if cron['comment']:
comment += ' {0}'.format(
cron['comment'].rstrip().replace('\n', '\n# '))
if cron['identifier']:
comment += ' {0}:{1}'.format(SALT_CRON_IDENTIFIER,
cron['identifier'])
comment += '\n'
ret.append(comment)
ret.append('{0}{1} {2}\n'.format(
cron['commented'] is True and '#DISABLED#' or '',
cron['spec'],
cron['cmd']
)
)
return ret
@ -317,7 +333,15 @@ def list_tab(user):
continue
dat['spec'] = comps[0]
dat['cmd'] = ' '.join(comps[1:])
dat['identifier'] = identifier
dat['comment'] = comment
dat['commented'] = False
if commented_cron_job:
dat['commented'] = True
ret['special'].append(dat)
identifier = None
comment = None
commented_cron_job = False
elif line.startswith('#'):
# It's a comment! Catch it!
comment_line = line.lstrip('# ')
@ -363,11 +387,17 @@ def list_tab(user):
ret['pre'].append(line)
return ret
# For consistency's sake
ls = salt.utils.alias_function(list_tab, 'ls')
def set_special(user, special, cmd):
def set_special(user,
special,
cmd,
commented=False,
comment=None,
identifier=None):
'''
Set up a special command in the crontab.
@ -379,11 +409,60 @@ def set_special(user, special, cmd):
'''
lst = list_tab(user)
for cron in lst['special']:
if special == cron['spec'] and cmd == cron['cmd']:
cid = _cron_id(cron)
if _cron_matched(cron, cmd, identifier):
test_setted_id = (
cron['identifier'] is None
and SALT_CRON_NO_IDENTIFIER
or cron['identifier'])
tests = [(cron['comment'], comment),
(cron['commented'], commented),
(identifier, test_setted_id),
(cron['spec'], special)]
if cid or identifier:
tests.append((cron['cmd'], cmd))
if any([_needs_change(x, y) for x, y in tests]):
rm_special(user, cmd, identifier=cid)
# Use old values when setting the new job if there was no
# change needed for a given parameter
if not _needs_change(cron['spec'], special):
special = cron['spec']
if not _needs_change(cron['commented'], commented):
commented = cron['commented']
if not _needs_change(cron['comment'], comment):
comment = cron['comment']
if not _needs_change(cron['cmd'], cmd):
cmd = cron['cmd']
if (
cid == SALT_CRON_NO_IDENTIFIER
):
if identifier:
cid = identifier
if (
cid == SALT_CRON_NO_IDENTIFIER
and cron['identifier'] is None
):
cid = None
cron['identifier'] = cid
if not cid or (
cid and not _needs_change(cid, identifier)
):
identifier = cid
jret = set_special(user, special, cmd, commented=commented,
comment=comment, identifier=identifier)
if jret == 'new':
return 'updated'
else:
return jret
return 'present'
spec = {'spec': special,
'cmd': cmd}
lst['special'].append(spec)
cron = {'spec': special,
'cmd': cmd,
'identifier': identifier,
'comment': comment,
'commented': commented}
lst['special'].append(cron)
comdat = _write_cron_lines(user, _render_tab(lst))
if comdat['retcode']:
# Failed to commit, return the error
@ -536,7 +615,7 @@ def set_job(user,
return 'new'
def rm_special(user, special, cmd):
def rm_special(user, cmd, special=None, identifier=None):
'''
Remove a special cron job for a specified user.
@ -544,22 +623,28 @@ def rm_special(user, special, cmd):
.. code-block:: bash
salt '*' cron.rm_job root @hourly /usr/bin/foo
salt '*' cron.rm_special root /usr/bin/foo
'''
lst = list_tab(user)
ret = 'absent'
rm_ = None
for ind in range(len(lst['special'])):
if lst['special'][ind]['cmd'] == cmd and \
lst['special'][ind]['spec'] == special:
lst['special'].pop(ind)
rm_ = ind
if rm_ is not None:
break
if _cron_matched(lst['special'][ind], cmd, identifier=identifier):
if special is None:
# No special param was specified
rm_ = ind
else:
if lst['special'][ind]['spec'] == special:
rm_ = ind
if rm_ is not None:
lst['special'].pop(rm_)
ret = 'removed'
comdat = _write_cron_lines(user, _render_tab(lst))
if comdat['retcode']:
# Failed to commit
return comdat['stderr']
comdat = _write_cron_lines(user, _render_tab(lst))
if comdat['retcode']:
# Failed to commit, return the error
return comdat['stderr']
return ret
@ -610,6 +695,7 @@ def rm_job(user,
return comdat['stderr']
return ret
rm = salt.utils.alias_function(rm_job, 'rm')

View file

@ -3836,8 +3836,15 @@ def get_managed(
parsed_scheme = urlparsed_source.scheme
parsed_path = os.path.join(
urlparsed_source.netloc, urlparsed_source.path).rstrip(os.sep)
unix_local_source = parsed_scheme in ('file', '')
if parsed_scheme and parsed_scheme.lower() in 'abcdefghijklmnopqrstuvwxyz':
if unix_local_source:
sfn = parsed_path
if not os.path.exists(sfn):
msg = 'Local file source {0} does not exist'.format(sfn)
return '', {}, msg
if parsed_scheme and parsed_scheme.lower() in string.ascii_lowercase:
parsed_path = ':'.join([parsed_scheme, parsed_path])
parsed_scheme = 'file'
@ -3845,9 +3852,10 @@ def get_managed(
source_sum = __salt__['cp.hash_file'](source, saltenv)
if not source_sum:
return '', {}, 'Source file {0} not found'.format(source)
elif not source_hash and parsed_scheme == 'file':
elif not source_hash and unix_local_source:
source_sum = _get_local_file_source_sum(parsed_path)
elif not source_hash and source.startswith(os.sep):
# This should happen on Windows
source_sum = _get_local_file_source_sum(source)
else:
if not skip_verify:
@ -4746,21 +4754,22 @@ def manage_file(name,
if source_sum and ('hsum' in source_sum):
source_sum['hsum'] = source_sum['hsum'].lower()
if source and not sfn:
# File is not present, cache it
sfn = __salt__['cp.cache_file'](source, saltenv)
if source:
if not sfn:
return _error(
ret, 'Source file \'{0}\' not found'.format(source))
htype = source_sum.get('hash_type', __opts__['hash_type'])
# Recalculate source sum now that file has been cached
source_sum = {
'hash_type': htype,
'hsum': get_hash(sfn, form=htype)
}
# File is not present, cache it
sfn = __salt__['cp.cache_file'](source, saltenv)
if not sfn:
return _error(
ret, 'Source file \'{0}\' not found'.format(source))
htype = source_sum.get('hash_type', __opts__['hash_type'])
# Recalculate source sum now that file has been cached
source_sum = {
'hash_type': htype,
'hsum': get_hash(sfn, form=htype)
}
if keep_mode:
if _urlparse(source).scheme in ('salt', 'file') \
or source.startswith('/'):
if _urlparse(source).scheme in ('salt', 'file', ''):
try:
mode = __salt__['cp.stat_file'](source, saltenv=saltenv, octal=True)
except Exception as exc:
@ -4790,7 +4799,7 @@ def manage_file(name,
# source, and we are not skipping checksum verification, then
# verify that it matches the specified checksum.
if not skip_verify \
and _urlparse(source).scheme not in ('salt', ''):
and _urlparse(source).scheme != 'salt':
dl_sum = get_hash(sfn, source_sum['hash_type'])
if dl_sum != source_sum['hsum']:
ret['comment'] = (
@ -4978,8 +4987,6 @@ def manage_file(name,
makedirs_(name, user=user, group=group, mode=dir_mode)
if source:
# It is a new file, set the diff accordingly
ret['changes']['diff'] = 'New file'
# Apply the new file
if not sfn:
sfn = __salt__['cp.cache_file'](source, saltenv)
@ -5003,6 +5010,8 @@ def manage_file(name,
)
ret['result'] = False
return ret
# It is a new file, set the diff accordingly
ret['changes']['diff'] = 'New file'
if not os.path.isdir(contain_dir):
if makedirs:
_set_mode_and_make_dirs(name, dir_mode, mode, user, group)

View file

@ -1084,8 +1084,8 @@ def build_routes(iface, **settings):
log.debug("IPv4 routes:\n{0}".format(opts4))
log.debug("IPv6 routes:\n{0}".format(opts6))
routecfg = template.render(routes=opts4)
routecfg6 = template.render(routes=opts6)
routecfg = template.render(routes=opts4, iface=iface)
routecfg6 = template.render(routes=opts6, iface=iface)
if settings['test']:
routes = _read_temp(routecfg)

View file

@ -319,7 +319,6 @@ def version(*names, **kwargs):
str: version string when a single package is specified.
dict: The package name(s) with the installed versions.
.. code-block:: cfg
{['<version>', '<version>', ]} OR
{'<package name>': ['<version>', '<version>', ]}

View file

@ -202,7 +202,14 @@ def _check_cron(user,
return 'present'
else:
for cron in lst['special']:
if special == cron['spec'] and cmd == cron['cmd']:
if _cron_matched(cron, cmd, identifier):
if any([_needs_change(x, y) for x, y in
((cron['spec'], special),
(cron['identifier'], identifier),
(cron['cmd'], cmd),
(cron['comment'], comment),
(cron['commented'], commented))]):
return 'update'
return 'present'
return 'absent'
@ -349,7 +356,12 @@ def present(name,
commented=commented,
identifier=identifier)
else:
data = __salt__['cron.set_special'](user, special, name)
data = __salt__['cron.set_special'](user=user,
special=special,
cmd=name,
comment=comment,
commented=commented,
identifier=identifier)
if data == 'present':
ret['comment'] = 'Cron {0} already present'.format(name)
return ret
@ -418,7 +430,7 @@ def absent(name,
if special is None:
data = __salt__['cron.rm_job'](user, name, identifier=identifier)
else:
data = __salt__['cron.rm_special'](user, special, name)
data = __salt__['cron.rm_special'](user, name, special=special, identifier=identifier)
if data == 'absent':
ret['comment'] = "Cron {0} already absent".format(name)

View file

@ -2091,6 +2091,9 @@ def managed(name,
'name': name,
'result': True}
if not name:
return _error(ret, 'Destination file name is required')
if mode is not None and salt.utils.is_windows():
return _error(ret, 'The \'mode\' option is not supported on Windows')
@ -2241,8 +2244,6 @@ def managed(name,
ret['comment'] = 'Error while applying template on contents'
return ret
if not name:
return _error(ret, 'Must provide name to file.managed')
user = _test_owner(kwargs, user=user)
if salt.utils.is_windows():

View file

@ -709,7 +709,7 @@ def edited_conf(name, lxc_conf=None, lxc_conf_unset=None):
# to keep this function around and cannot officially remove it. Progress of
# the new function will be tracked in https://github.com/saltstack/salt/issues/35523
salt.utils.warn_until(
'Oxygen',
'Fluorine',
'This state is unsuitable for setting parameters that appear more '
'than once in an LXC config file, or parameters which must appear in '
'a certain order (such as when configuring more than one network '

View file

@ -5,5 +5,6 @@
/{{route.netmask}}
{%- endif -%}
{%- if route.gateway %} via {{route.gateway}}
{%- else %} dev {{iface}}
{%- endif %}
{% endfor -%}

View file

@ -1143,10 +1143,10 @@ def format_call(fun,
continue
extra[key] = copy.deepcopy(value)
# We'll be showing errors to the users until Salt Oxygen comes out, after
# We'll be showing errors to the users until Salt Fluorine comes out, after
# which, errors will be raised instead.
warn_until(
'Oxygen',
'Fluorine',
'It\'s time to start raising `SaltInvocationError` instead of '
'returning warnings',
# Let's not show the deprecation warning on the console, there's no
@ -1183,7 +1183,7 @@ def format_call(fun,
'{0}. If you were trying to pass additional data to be used '
'in a template context, please populate \'context\' with '
'\'key: value\' pairs. Your approach will work until Salt '
'Oxygen is out.{1}'.format(
'Fluorine is out.{1}'.format(
msg,
'' if 'full' not in ret else ' Please update your state files.'
)

View file

@ -74,9 +74,9 @@ def _test_managed_file_mode_keep_helper(testcase, local=False):
# Get the current mode so that we can put the file back the way we
# found it when we're done.
grail_fs_mode = int(testcase.run_function('file.get_mode', [grail_fs_path]), 8)
initial_mode = 504 # 0770 octal
new_mode_1 = 384 # 0600 octal
new_mode_2 = 420 # 0644 octal
initial_mode = 0o770
new_mode_1 = 0o600
new_mode_2 = 0o644
# Set the initial mode, so we can be assured that when we set the mode
# to "keep", we're actually changing the permissions of the file to the
@ -567,6 +567,84 @@ class FileTest(ModuleCase, SaltReturnAssertsMixin):
if os.path.exists('/tmp/sudoers'):
os.remove('/tmp/sudoers')
def test_managed_local_source_with_source_hash(self):
'''
Make sure that we enforce the source_hash even with local files
'''
name = os.path.join(TMP, 'local_source_with_source_hash')
local_path = os.path.join(FILES, 'file', 'base', 'grail', 'scene33')
actual_hash = '567fd840bf1548edc35c48eb66cdd78bfdfcccff'
# Reverse the actual hash
bad_hash = actual_hash[::-1]
def remove_file():
try:
os.remove(name)
except OSError as exc:
if exc.errno != errno.ENOENT:
raise
def do_test(clean=False):
for proto in ('file://', ''):
source = proto + local_path
log.debug('Trying source %s', source)
try:
ret = self.run_state(
'file.managed',
name=name,
source=source,
source_hash='sha1={0}'.format(bad_hash))
self.assertSaltFalseReturn(ret)
ret = ret[next(iter(ret))]
# Shouldn't be any changes
self.assertFalse(ret['changes'])
# Check that we identified a hash mismatch
self.assertIn(
'does not match actual checksum', ret['comment'])
ret = self.run_state(
'file.managed',
name=name,
source=source,
source_hash='sha1={0}'.format(actual_hash))
self.assertSaltTrueReturn(ret)
finally:
if clean:
remove_file()
remove_file()
log.debug('Trying with nonexistant destination file')
do_test()
log.debug('Trying with destination file already present')
with salt.utils.fopen(name, 'w'):
pass
try:
do_test(clean=False)
finally:
remove_file()
def test_managed_local_source_does_not_exist(self):
'''
Make sure that we exit gracefully when a local source doesn't exist
'''
name = os.path.join(TMP, 'local_source_does_not_exist')
local_path = os.path.join(FILES, 'file', 'base', 'grail', 'scene99')
for proto in ('file://', ''):
source = proto + local_path
log.debug('Trying source %s', source)
ret = self.run_state(
'file.managed',
name=name,
source=source)
self.assertSaltFalseReturn(ret)
ret = ret[next(iter(ret))]
# Shouldn't be any changes
self.assertFalse(ret['changes'])
# Check that we identified a hash mismatch
self.assertIn(
'does not exist', ret['comment'])
def test_directory(self):
'''
file.directory

View file

@ -577,7 +577,7 @@ class TestFileState(TestCase, LoaderModuleMockMixin):
'file.copy': mock_cp,
'file.manage_file': mock_ex,
'cmd.run_all': mock_cmd_fail}):
comt = ('Must provide name to file.managed')
comt = ('Destination file name is required')
ret.update({'comment': comt, 'name': '', 'pchanges': {}})
self.assertDictEqual(filestate.managed(''), ret)