mirror of
https://github.com/saltstack/salt.git
synced 2025-04-17 10:10:20 +00:00
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:
commit
23c5a4ca3e
12 changed files with 239 additions and 53 deletions
|
@ -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)
|
||||
|
||||
|
|
|
@ -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')
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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>', ]}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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():
|
||||
|
||||
|
|
|
@ -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 '
|
||||
|
|
|
@ -5,5 +5,6 @@
|
|||
/{{route.netmask}}
|
||||
{%- endif -%}
|
||||
{%- if route.gateway %} via {{route.gateway}}
|
||||
{%- else %} dev {{iface}}
|
||||
{%- endif %}
|
||||
{% endfor -%}
|
||||
|
|
|
@ -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.'
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue