Merge branch '2018.3' into 'fluorine'

Conflicts:
  - salt/modules/cmdmod.py
  - salt/utils/schedule.py
  - tests/integration/files/conf/master
  - tests/integration/netapi/rest_tornado/test_app.py
  - tests/integration/runners/test_state.py
  - tests/integration/scheduler/test_eval.py
This commit is contained in:
rallytime 2018-08-10 14:18:34 -04:00
commit 5ff1ddb0dd
No known key found for this signature in database
GPG key ID: E8F1A4B90D0DEA19
48 changed files with 1492 additions and 286 deletions

View file

@ -11,16 +11,11 @@ group :docker do
gem 'kitchen-docker', :git => 'https://github.com/test-kitchen/kitchen-docker.git'
end
group :opennebula do
gem 'kitchen-opennebula', '>=0.2.3'
gem 'xmlrpc'
end
group :windows do
gem 'vagrant-wrapper'
gem 'kitchen-vagrant'
gem 'winrm', '~>2.0'
gem 'winrm-fs', :git => 'https://github.com/WinRb/winrm-fs.git'
gem 'winrm-fs', '~>1.2.1'
end
group :ec2 do

View file

@ -103,9 +103,9 @@ Logfile logging log level. One of \fBall\fP, \fBgarbage\fP, \fBtrace\fP,
.UNINDENT
.SH SEE ALSO
.sp
\fBsalt\-api(7)\fP
\fBsalt(7)\fP
\fBsalt\-master(1)\fP
\fIsalt\-api(7)\fP
\fIsalt(7)\fP
\fIsalt\-master(1)\fP
.SH AUTHOR
Thomas S. Hatch <thatch45@gmail.com> and many others, please see the Authors file
.\" Generated by docutils manpage writer.

View file

@ -265,9 +265,9 @@ output. Set to True or False. Default: none.
.UNINDENT
.SH SEE ALSO
.sp
\fBsalt(1)\fP
\fBsalt\-master(1)\fP
\fBsalt\-minion(1)\fP
\fIsalt(1)\fP
\fIsalt\-master(1)\fP
\fIsalt\-minion(1)\fP
.SH AUTHOR
Thomas S. Hatch <thatch45@gmail.com> and many others, please see the Authors file
.\" Generated by docutils manpage writer.

View file

@ -387,10 +387,10 @@ salt\-cloud \-m /path/to/cloud.map \-Q
.UNINDENT
.SH SEE ALSO
.sp
\fBsalt\-cloud(7)\fP
\fBsalt(7)\fP
\fBsalt\-master(1)\fP
\fBsalt\-minion(1)\fP
\fIsalt\-cloud(7)\fP
\fIsalt(7)\fP
\fIsalt\-master(1)\fP
\fIsalt\-minion(1)\fP
.SH AUTHOR
Thomas S. Hatch <thatch45@gmail.com> and many others, please see the Authors file
.\" Generated by docutils manpage writer.

View file

@ -201,9 +201,9 @@ New in version 2016.3.7,2016.11.6,2017.7.0.
.UNINDENT
.SH SEE ALSO
.sp
\fBsalt(1)\fP
\fBsalt\-master(1)\fP
\fBsalt\-minion(1)\fP
\fIsalt(1)\fP
\fIsalt\-master(1)\fP
\fIsalt\-minion(1)\fP
.SH AUTHOR
Thomas S. Hatch <thatch45@gmail.com> and many others, please see the Authors file
.\" Generated by docutils manpage writer.

View file

@ -340,9 +340,9 @@ Auto\-create a signing key\-pair if it does not yet exist
.UNINDENT
.SH SEE ALSO
.sp
\fBsalt(7)\fP
\fBsalt\-master(1)\fP
\fBsalt\-minion(1)\fP
\fIsalt(7)\fP
\fIsalt\-master(1)\fP
\fIsalt\-minion(1)\fP
.SH AUTHOR
Thomas S. Hatch <thatch45@gmail.com> and many others, please see the Authors file
.\" Generated by docutils manpage writer.

View file

@ -108,9 +108,9 @@ Logfile logging log level. One of \fBall\fP, \fBgarbage\fP, \fBtrace\fP,
.UNINDENT
.SH SEE ALSO
.sp
\fBsalt(1)\fP
\fBsalt(7)\fP
\fBsalt\-minion(1)\fP
\fIsalt(1)\fP
\fIsalt(7)\fP
\fIsalt\-minion(1)\fP
.SH AUTHOR
Thomas S. Hatch <thatch45@gmail.com> and many others, please see the Authors file
.\" Generated by docutils manpage writer.

View file

@ -109,9 +109,9 @@ Logfile logging log level. One of \fBall\fP, \fBgarbage\fP, \fBtrace\fP,
.UNINDENT
.SH SEE ALSO
.sp
\fBsalt(1)\fP
\fBsalt(7)\fP
\fBsalt\-master(1)\fP
\fIsalt(1)\fP
\fIsalt(7)\fP
\fIsalt\-master(1)\fP
.SH AUTHOR
Thomas S. Hatch <thatch45@gmail.com> and many others, please see the Authors file
.\" Generated by docutils manpage writer.

View file

@ -116,10 +116,10 @@ Logfile logging log level. One of \fBall\fP, \fBgarbage\fP, \fBtrace\fP,
.UNINDENT
.SH SEE ALSO
.sp
\fBsalt(1)\fP
\fBsalt(7)\fP
\fBsalt\-master(1)\fP
\fBsalt\-minion(1)\fP
\fIsalt(1)\fP
\fIsalt(7)\fP
\fIsalt\-master(1)\fP
\fIsalt\-minion(1)\fP
.SH AUTHOR
Thomas S. Hatch <thatch45@gmail.com> and many others, please see the Authors file
.\" Generated by docutils manpage writer.

View file

@ -114,9 +114,9 @@ Logfile logging log level. One of \fBall\fP, \fBgarbage\fP, \fBtrace\fP,
.UNINDENT
.SH SEE ALSO
.sp
\fBsalt(1)\fP
\fBsalt\-master(1)\fP
\fBsalt\-minion(1)\fP
\fIsalt(1)\fP
\fIsalt\-master(1)\fP
\fIsalt\-minion(1)\fP
.SH AUTHOR
Thomas S. Hatch <thatch45@gmail.com> and many others, please see the Authors file
.\" Generated by docutils manpage writer.

View file

@ -348,9 +348,9 @@ output. Set to True or False. Default: none.
.UNINDENT
.SH SEE ALSO
.sp
\fBsalt(7)\fP
\fBsalt\-master(1)\fP
\fBsalt\-minion(1)\fP
\fIsalt(7)\fP
\fIsalt\-master(1)\fP
\fIsalt\-minion(1)\fP
.SH AUTHOR
Thomas S. Hatch <thatch45@gmail.com> and many others, please see the Authors file
.\" Generated by docutils manpage writer.

View file

@ -110,9 +110,9 @@ Logfile logging log level. One of \fBall\fP, \fBgarbage\fP, \fBtrace\fP,
.UNINDENT
.SH SEE ALSO
.sp
\fBsalt(1)\fP
\fBsalt\-master(1)\fP
\fBsalt\-minion(1)\fP
\fIsalt(1)\fP
\fIsalt\-master(1)\fP
\fIsalt\-minion(1)\fP
.SH AUTHOR
Thomas S. Hatch <thatch45@gmail.com> and many others, please see the Authors file
.\" Generated by docutils manpage writer.

View file

@ -50,17 +50,17 @@ invokes that script.
.SH OPTIONS
.SH SEE ALSO
.sp
\fBsalt\-api(1)\fP
\fBsalt\-call(1)\fP
\fBsalt\-cloud(1)\fP
\fBsalt\-cp(1)\fP
\fBsalt\-key(1)\fP
\fBsalt\-main(1)\fP
\fBsalt\-master(1)\fP
\fBsalt\-minion(1)\fP
\fBsalt\-run(1)\fP
\fBsalt\-ssh(1)\fP
\fBsalt\-syndic(1)\fP
\fIsalt\-api(1)\fP
\fIsalt\-call(1)\fP
\fIsalt\-cloud(1)\fP
\fIsalt\-cp(1)\fP
\fIsalt\-key(1)\fP
\fIsalt\-main(1)\fP
\fIsalt\-master(1)\fP
\fIsalt\-minion(1)\fP
\fIsalt\-run(1)\fP
\fIsalt\-ssh(1)\fP
\fIsalt\-syndic(1)\fP
.SH AUTHOR
Thomas S. Hatch <thatch45@gmail.com> and many others, please see the Authors file
.\" Generated by docutils manpage writer.

View file

@ -95,16 +95,6 @@ the started execution and complete.
.UNINDENT
.INDENT 0.0
.TP
.B \-\-state\-output=STATE_OUTPUT
New in version 0.17.
.sp
Override the configured \fBstate_output\fP value for minion output. One of
\fBfull\fP, \fBterse\fP, \fBmixed\fP, \fBchanges\fP or \fBfilter\fP\&. Default:
\fBfull\fP\&.
.UNINDENT
.INDENT 0.0
.TP
.B \-\-subset=SUBSET
Execute the routine on a random subset of the targeted minions. The
minions will be verified that they have the named function before
@ -344,9 +334,9 @@ output. Set to True or False. Default: none.
.UNINDENT
.SH SEE ALSO
.sp
\fBsalt(7)\fP
\fBsalt\-master(1)\fP
\fBsalt\-minion(1)\fP
\fIsalt(7)\fP
\fIsalt\-master(1)\fP
\fIsalt\-minion(1)\fP
.SH AUTHOR
Thomas S. Hatch <thatch45@gmail.com> and many others, please see the Authors file
.\" Generated by docutils manpage writer.

View file

@ -132,9 +132,9 @@ in that directory which describes them.
.UNINDENT
.SH SEE ALSO
.sp
\fBsalt(1)\fP
\fBsalt\-master(1)\fP
\fBsalt\-minion(1)\fP
\fIsalt(1)\fP
\fIsalt\-master(1)\fP
\fIsalt\-minion(1)\fP
.SH AUTHOR
Thomas S. Hatch <thatch45@gmail.com> and many others, please see the Authors file
.\" Generated by docutils manpage writer.

View file

@ -21,7 +21,7 @@ management.
The default job cache is a temporary cache and jobs will be stored for 24
hours. If the default cache needs to store jobs for a different period the
time can be easily adjusted by changing the `keep_jobs` parameter in the
time can be easily adjusted by changing the ``keep_jobs`` parameter in the
Salt Master configuration file. The value passed in is measured via hours:
@ -47,7 +47,7 @@ checking for and preventing JID collisions.
The default location for the job cache is in the ``/var/cache/salt/master/jobs/``
directory.
Setting the :conf_master:`job_cache`` to ``False`` in addition to setting
Setting the :conf_master:`job_cache` to ``False`` in addition to setting
the :conf_master:`keep_jobs` option to a smaller value, such as ``1``, in the Salt
Master configuration file will reduce the size of the Default Job Cache, and thus
the burden on the Salt Master.

View file

@ -31,3 +31,15 @@ renderer. For example:
foo:
bar.baz:
- some_arg: {{ mydict|tojson }}
MacOSX escape characters with runas
===================================
You are now required to escape quotes when using the runas argument with the
cmd module on macosx.
Example:
.. code-block:: bash
cmd.run 'echo '\''h=\"baz\"'\''' runas=macuser

View file

@ -79,6 +79,12 @@ def beacon(config):
- tags:
<tag>:
regex: <pattern>
.. note::
regex matching is based on the `re`_ module
.. _re: https://docs.python.org/3.6/library/re.html#regular-expression-syntax
'''
_config = {}
list(map(_config.update, config))

View file

@ -141,7 +141,7 @@ class Shell(object):
options.append('UserKnownHostsFile={0}'.format(known_hosts))
if self.port:
options.append('Port={0}'.format(self.port))
if self.priv:
if self.priv and self.priv != 'agent-forwarding':
options.append('IdentityFile={0}'.format(self.priv))
if self.user:
options.append('User={0}'.format(self.user))

View file

@ -35,7 +35,7 @@ import salt.utils.user
# pylint: disable=import-error,no-name-in-module,redefined-builtin
from salt.ext import six
from salt.ext.six.moves import input
from salt.ext.six.moves import input, zip_longest
# pylint: enable=import-error,no-name-in-module,redefined-builtin
# Import third party libs
@ -156,15 +156,24 @@ class KeyCLI(object):
self.auth = low
def _get_args_kwargs(self, fun, args=None):
argspec = salt.utils.args.get_function_argspec(fun)
if args is None:
argspec = salt.utils.args.get_function_argspec(fun)
args = []
if argspec.args:
for arg in argspec.args:
args.append(self.opts.get(arg))
args, kwargs = salt.minion.load_args_and_kwargs(
fun,
args)
# Iterate in reverse order to ensure we get the correct default
# value for the positional argument.
for arg, default in zip_longest(reversed(argspec.args),
reversed(argspec.defaults or ())):
args.append(self.opts.get(arg, default))
# Reverse the args so that they are in the correct order
args = args[::-1]
if argspec.keywords is None:
kwargs = {}
else:
args, kwargs = salt.minion.load_args_and_kwargs(
fun,
args)
return args, kwargs
def _run_cmd(self, cmd, args=None):

View file

@ -964,12 +964,23 @@ def run(cmd,
:param str runas: Specify an alternate user to run the command. The default
behavior is to run as the user under which Salt is running.
.. warning::
For versions 2018.3.3 and above on macosx while using runas,
to pass special characters to the command you need to escape
the characters on the shell.
Example:
.. code-block:: bash
cmd.run 'echo '\''h=\"baz\"'\''' runas=macuser
:param str group: Group to run command as. Not currently supported
on Windows.
:param str password: Windows only. Only required when the minion proccess
is running under a non-privileged account. This parameter will be
ignored on non-Windows platforms.
:param str password: Windows only. Required when specifying ``runas``. This
parameter will be ignored on non-Windows platforms.
.. versionadded:: 2016.3.0
@ -1222,6 +1233,18 @@ def shell(cmd,
on a Windows minion you must also use the ``password`` argument, and
the target user account must be in the Administrators group.
.. warning::
For versions 2018.3.3 and above on macosx while using runas,
to pass special characters to the command you need to escape
the characters on the shell.
Example:
.. code-block:: bash
cmd.shell 'echo '\\''h=\\"baz\\"'\\\''' runas=macuser
:param str group: Group to run command as. Not currently supported
on Windows.
@ -1440,6 +1463,18 @@ def run_stdout(cmd,
on a Windows minion you must also use the ``password`` argument, and
the target user account must be in the Administrators group.
.. warning::
For versions 2018.3.3 and above on macosx while using runas,
to pass special characters to the command you need to escape
the characters on the shell.
Example:
.. code-block:: bash
cmd.run_stdout 'echo '\\''h=\\"baz\\"'\\\''' runas=macuser
:param str password: Windows only. Required when specifying ``runas``. This
parameter will be ignored on non-Windows platforms.
@ -1636,6 +1671,18 @@ def run_stderr(cmd,
on a Windows minion you must also use the ``password`` argument, and
the target user account must be in the Administrators group.
.. warning::
For versions 2018.3.3 and above on macosx while using runas,
to pass special characters to the command you need to escape
the characters on the shell.
Example:
.. code-block:: bash
cmd.run_stderr 'echo '\\''h=\\"baz\\"'\\\''' runas=macuser
:param str password: Windows only. Required when specifying ``runas``. This
parameter will be ignored on non-Windows platforms.
@ -1834,6 +1881,18 @@ def run_all(cmd,
on a Windows minion you must also use the ``password`` argument, and
the target user account must be in the Administrators group.
.. warning::
For versions 2018.3.3 and above on macosx while using runas,
to pass special characters to the command you need to escape
the characters on the shell.
Example:
.. code-block:: bash
cmd.run_all 'echo '\\''h=\\"baz\\"'\\\''' runas=macuser
:param str password: Windows only. Required when specifying ``runas``. This
parameter will be ignored on non-Windows platforms.
@ -2054,6 +2113,18 @@ def retcode(cmd,
on a Windows minion you must also use the ``password`` argument, and
the target user account must be in the Administrators group.
.. warning::
For versions 2018.3.3 and above on macosx while using runas,
to pass special characters to the command you need to escape
the characters on the shell.
Example:
.. code-block:: bash
cmd.retcode 'echo '\\''h=\\"baz\\"'\\\''' runas=macuser
:param str password: Windows only. Required when specifying ``runas``. This
parameter will be ignored on non-Windows platforms.
@ -2469,9 +2540,12 @@ def script(source,
os.chmod(path, 320)
os.chown(path, __salt__['file.user_to_uid'](runas), -1)
path = _cmd_quote(path)
if salt.utils.platform.is_windows() and shell.lower() != 'powershell':
cmd_path = _cmd_quote(path, escape=False)
else:
cmd_path = _cmd_quote(path)
ret = _run(path + ' ' + six.text_type(args) if args else path,
ret = _run(cmd_path + ' ' + six.text_type(args) if args else cmd_path,
cwd=cwd,
stdin=stdin,
output_encoding=output_encoding,
@ -3936,6 +4010,18 @@ def run_bg(cmd,
on a Windows minion you must also use the ``password`` argument, and
the target user account must be in the Administrators group.
.. warning::
For versions 2018.3.3 and above on macosx while using runas,
to pass special characters to the command you need to escape
the characters on the shell.
Example:
.. code-block:: bash
cmd.run_bg 'echo '\''h=\"baz\"'\''' runas=macuser
:param str password: Windows only. Required when specifying ``runas``. This
parameter will be ignored on non-Windows platforms.

View file

@ -4454,7 +4454,8 @@ def check_perms(name, ret, user, group, mode, attrs=None, follow_symlinks=False)
perms['lmode'] = salt.utils.files.normalize_mode(cur['mode'])
is_dir = os.path.isdir(name)
if not salt.utils.platform.is_windows() and not is_dir:
is_link = os.path.islink(name)
if not salt.utils.platform.is_windows() and not is_dir and not is_link:
lattrs = lsattr(name)
if lattrs is not None:
# List attributes on file
@ -4991,7 +4992,7 @@ def get_diff(file1,
)
args = []
for filename in files:
for filename in paths:
try:
with salt.utils.files.fopen(filename, 'rb') as fp_:
args.append(fp_.readlines())
@ -5009,12 +5010,12 @@ def get_diff(file1,
elif not show_changes:
ret = '<show_changes=False>'
else:
bdiff = _binary_replace(*files)
bdiff = _binary_replace(*paths) # pylint: disable=no-value-for-parameter
if bdiff:
ret = bdiff
else:
if show_filenames:
args.extend(files)
args.extend(paths)
ret = __utils__['stringutils.get_diff'](*args)
return ret
return ''

View file

@ -17,12 +17,14 @@ import salt.utils.path
import salt.utils.platform
import salt.utils.mount
import salt.utils.stringutils
from salt.utils.odict import OrderedDict
from salt.exceptions import CommandNotFoundError, CommandExecutionError
# Import 3rd-party libs
from salt.ext import six
from salt.ext.six.moves import filter, zip # pylint: disable=import-error,redefined-builtin
# Set up logger
log = logging.getLogger(__name__)
@ -434,6 +436,141 @@ class _vfstab_entry(object):
return True
class _FileSystemsEntry(object):
'''
Utility class for manipulating filesystem entries. Primarily we're parsing,
formatting, and comparing lines. Parsing emits dicts expected from
fstab() or raises a ValueError.
Note: We'll probably want to use os.normpath and os.normcase on 'name'
'''
class ParseError(ValueError):
'''
Error raised when a line isn't parsible as an fstab entry
'''
filesystems_keys = ('device', 'name', 'fstype', 'vfstype', 'opts', 'mount')
# preserve data format of filesystems
compatibility_keys = ('dev', 'dev', 'name', 'fstype', 'vfstype', 'opts', 'mount', 'type', 'vfs', 'account', 'boot', 'check', 'free', 'nodename', 'quota', 'size', 'vol', 'log')
@classmethod
def dict_from_lines(cls, lines, keys=filesystems_keys):
if len(lines) < 2:
raise ValueError('Invalid number of lines: {0}'.format(lines))
if not keys:
# if empty force default filesystems_keys
keys = _FileSystemsEntry.filesystems_keys
elif len(keys) < 6:
raise ValueError('Invalid key name array: {0}'.format(keys))
blk_lines = lines
orddict = OrderedDict()
orddict['name'] = blk_lines[0].split(':')[0].strip()
blk_lines.pop(0)
for line in blk_lines:
if line.startswith('#'):
raise cls.ParseError("Comment!")
comps = line.split('= ')
if len(comps) != 2:
raise cls.ParseError("Invalid Entry!")
key_name = comps[0].strip()
if key_name in keys:
orddict[key_name] = comps[1].strip()
else:
raise ValueError('Invalid name for use in filesystems: {0}'.format(key_name))
return orddict
@classmethod
def dict_from_cmd_line(cls, ipargs, keys):
cmdln_dict = ipargs
if keys:
for key, value in keys:
# ignore unknown or local scope keys
if key.startswith('__'):
continue
if key in _FileSystemsEntry.compatibility_keys:
cmdln_dict[key] = value
return cmdln_dict
@classmethod
def from_line(cls, *args, **kwargs):
return cls(** cls.dict_from_cmd_line(*args, **kwargs))
@classmethod
def dict_to_lines(cls, fsys_dict_entry):
entry = fsys_dict_entry
strg_out = entry['name'] + ':' + os.linesep
for k, v in six.viewitems(entry):
if 'name' not in k:
strg_out += '\t{0}\t\t= {1}'.format(k, v) + os.linesep
strg_out += os.linesep
return six.text_type(strg_out)
def dict_from_entry(self):
ret = OrderedDict()
ret[self.criteria['name']] = self.criteria
return ret
def __str__(self):
'''
String value, only works for full repr
'''
return self.dict_to_lines(self.criteria)
def __repr__(self):
'''
Always works
'''
return repr(self.criteria)
def pick(self, keys):
'''
Returns an instance with just those keys
'''
subset = dict([(key, self.criteria[key]) for key in keys])
return self.__class__(**subset)
def __init__(self, **criteria):
'''
Store non-empty, non-null values to use as filter
'''
items = [key_value for key_value in six.iteritems(criteria) if key_value[1] is not None]
items = [(key_value1[0], six.text_type(key_value1[1])) for key_value1 in items]
self.criteria = OrderedDict(items)
@staticmethod
def norm_path(path):
'''
Resolve equivalent paths equivalently
'''
return os.path.normcase(os.path.normpath(path))
def match(self, fsys_view):
'''
Compare potentially partial criteria against built filesystems entry dictionary
'''
evalue_dict = fsys_view[1]
for key, value in six.viewitems(self.criteria):
if key in evalue_dict:
if evalue_dict[key] != value:
return False
else:
return False
return True
def __getitem__(self, key):
'''
Return value for input key
'''
return self.criteria[key]
def fstab(config='/etc/fstab'):
'''
.. versionchanged:: 2016.3.2
@ -1072,8 +1209,12 @@ def mount(name, device, mkmnt=False, fstype='', opts='defaults', user=None, util
lopts = ','.join(opts)
args = '-o {0}'.format(lopts)
# use of fstype on AIX is with /etc/filesystems
if fstype and 'AIX' not in __grains__['os']:
# use of fstype on AIX differs from typical Linux use of -t functionality
# AIX uses -v vfsname, -t fstype mounts all with fstype in /etc/filesystems
if 'AIX' in __grains__['os']:
if fstype:
args += ' -v {0}'.format(fstype)
else:
args += ' -t {0}'.format(fstype)
cmd = 'mount {0} {1} {2} '.format(args, device, name)
out = __salt__['cmd.run_all'](cmd, runas=user, python_shell=False)
@ -1117,9 +1258,15 @@ def remount(name, device, mkmnt=False, fstype='', opts='defaults', user=None):
lopts = ','.join(opts)
args = '-o {0}'.format(lopts)
# use of fstype on AIX is with /etc/filesystems
if fstype and 'AIX' not in __grains__['os']:
# use of fstype on AIX differs from typical Linux use of -t functionality
# AIX uses -v vfsname, -t fstype mounts all with fstype in /etc/filesystems
if 'AIX' in __grains__['os']:
if fstype:
args += ' -v {0}'.format(fstype)
args += ' -o remount'
else:
args += ' -t {0}'.format(fstype)
if __grains__['os'] not in ['OpenBSD', 'MacOS', 'Darwin'] or force_mount:
cmd = 'mount {0} {1} {2} '.format(args, device, name)
else:
@ -1424,3 +1571,278 @@ def delete_mount_cache(real_name):
if not cache_write:
raise CommandExecutionError('Unable to write mount cache.')
return True
def _filesystems(config='/etc/filesystems', leading_key=True):
'''
Return the contents of the filesystems in an OrderedDict
config
File containing filesystem infomation
leading_key
True return dictionary keyed by 'name' and value as dictionary with other keys, values (name excluded)
OrderedDict({ '/dir' : OrderedDict({'dev': '/dev/hd8', .... }}))
False return dictionary keyed by 'name' and value as dictionary with all keys, values (name included)
OrderedDict({ '/dir' : OrderedDict({'name': '/dir', 'dev': '/dev/hd8', ... })})
'''
ret = OrderedDict()
lines = []
parsing_block = False
if not os.path.isfile(config) or 'AIX' not in __grains__['kernel']:
return ret
# read in block of filesystems, block starts with '/' till empty line
with salt.utils.files.fopen(config) as ifile:
for line in ifile:
line = salt.utils.stringutils.to_unicode(line)
# skip till first entry
if not line.startswith('/') and not parsing_block:
continue
if line.startswith('/'):
parsing_block = True
lines.append(line)
elif not line.split():
parsing_block = False
try:
entry = _FileSystemsEntry.dict_from_lines(
lines,
_FileSystemsEntry.compatibility_keys)
lines = []
if 'opts' in entry:
entry['opts'] = entry['opts'].split(',')
while entry['name'] in ret:
entry['name'] += '_'
if leading_key:
ret[entry.pop('name')] = entry
else:
ret[entry['name']] = entry
except _FileSystemsEntry.ParseError:
pass
else:
lines.append(line)
return ret
def filesystems(config='/etc/filesystems'):
'''
.. versionadded:: 2018.3.3
List the contents of the filesystems
CLI Example:
.. code-block:: bash
salt '*' mount.filesystems
'''
ret = {}
if 'AIX' not in __grains__['kernel']:
return ret
ret_dict = _filesystems(config)
if ret_dict:
ret_key = next(iter(ret_dict.keys()))
ret = {ret_key: dict(ret_dict[ret_key])}
return ret
def set_filesystems(
name,
device,
vfstype,
opts='-',
mount='true',
config='/etc/filesystems',
test=False,
match_on='auto',
**kwargs):
'''
.. versionadded:: 2018.3.3
Verify that this mount is represented in the filesystems, change the mount
to match the data passed, or add the mount if it is not present on AIX
Provide information if the path is mounted
:param name: The name of the mount point where the device is mounted.
:param device: The device that is being mounted.
:param vfstype: The file system that is used (AIX has two fstypes, fstype and vfstype - similar to Linux fstype)
:param opts: Additional options used when mounting the device.
:param mount: Mount if not mounted, default True.
:param config: Configuration file, default /etc/filesystems.
:param match: File systems type to match on, default auto
CLI Example:
.. code-block:: bash
salt '*' mount.set_filesystems /mnt/foo /dev/sdz1 jfs2
'''
# Fix the opts type if it is a list
if isinstance(opts, list):
opts = ','.join(opts)
# preserve arguments for updating
entry_args = {
'name': name,
'dev': device.replace('\\ ', '\\040'),
'vfstype': vfstype,
'opts': opts,
'mount': mount,
}
view_lines = []
ret = None
if 'AIX' not in __grains__['kernel']:
return ret
# Transform match_on into list--items will be checked later
if isinstance(match_on, list):
pass
elif not isinstance(match_on, six.string_types):
raise CommandExecutionError('match_on must be a string or list of strings')
elif match_on == 'auto':
# Try to guess right criteria for auto....
# added IBM types from sys/vmount.h after btrfs
# NOTE: missing some special fstypes here
specialFSes = frozenset([
'none',
'tmpfs',
'sysfs',
'proc',
'fusectl',
'debugfs',
'securityfs',
'devtmpfs',
'cgroup',
'btrfs',
'cdrfs',
'procfs',
'jfs',
'jfs2',
'nfs',
'sfs',
'nfs3',
'cachefs',
'udfs',
'cifs',
'namefs',
'pmemfs',
'ahafs',
'nfs4',
'autofs',
'stnfs'])
if vfstype in specialFSes:
match_on = ['name']
else:
match_on = ['dev']
else:
match_on = [match_on]
# generate entry and criteria objects, handle invalid keys in match_on
entry_ip = _FileSystemsEntry.from_line(entry_args, kwargs)
try:
criteria = entry_ip.pick(match_on)
except KeyError:
filterFn = lambda key: key not in _FileSystemsEntry.compatibility_keys
invalid_keys = filter(filterFn, match_on)
raise CommandExecutionError('Unrecognized keys in match_on: "{0}"'.format(invalid_keys))
# parse file, use ret to cache status
if not os.path.isfile(config):
raise CommandExecutionError('Bad config file "{0}"'.format(config))
# read in block of filesystem, block starts with '/' till empty line
try:
fsys_filedict = _filesystems(config, False)
for fsys_view in six.viewitems(fsys_filedict):
if criteria.match(fsys_view):
ret = 'present'
if entry_ip.match(fsys_view):
view_lines.append(fsys_view)
else:
ret = 'change'
kv = entry_ip['name']
view_lines.append((kv, entry_ip))
else:
view_lines.append(fsys_view)
except (IOError, OSError) as exc:
raise CommandExecutionError('Couldn\'t read from {0}: {1}'.format(config, exc))
# add line if not present or changed
if ret is None:
for dict_view in six.viewitems(entry_ip.dict_from_entry()):
view_lines.append(dict_view)
ret = 'new'
if ret != 'present': # ret in ['new', 'change']:
try:
with salt.utils.files.fopen(config, 'wb') as ofile:
# The line was changed, commit it!
for fsys_view in view_lines:
entry = fsys_view[1]
mystrg = _FileSystemsEntry.dict_to_lines(entry)
ofile.writelines(salt.utils.data.encode(mystrg))
except (IOError, OSError):
raise CommandExecutionError('File not writable {0}'.format(config))
return ret
def rm_filesystems(name, device, config='/etc/filesystems'):
'''
.. versionadded:: 2018.3.3
Remove the mount point from the filesystems
CLI Example:
.. code-block:: bash
salt '*' mount.rm_filesystems /mnt/foo /dev/sdg
'''
modified = False
view_lines = []
if 'AIX' not in __grains__['kernel']:
return modified
criteria = _FileSystemsEntry(name=name, dev=device)
try:
fsys_filedict = _filesystems(config, False)
for fsys_view in six.viewitems(fsys_filedict):
try:
if criteria.match(fsys_view):
modified = True
else:
view_lines.append(fsys_view)
except _FileSystemsEntry.ParseError:
view_lines.append(fsys_view)
except (IOError, OSError) as exc:
raise CommandExecutionError("Couldn't read from {0}: {1}".format(config, exc))
if modified:
try:
with salt.utils.files.fopen(config, 'wb') as ofile:
for fsys_view in view_lines:
entry = fsys_view[1]
mystrg = _FileSystemsEntry.dict_to_lines(entry)
ofile.writelines(salt.utils.data.encode(mystrg))
except (IOError, OSError) as exc:
raise CommandExecutionError("Couldn't write to {0}: {1}".format(config, exc))
return modified

View file

@ -35,39 +35,24 @@ def __virtual__():
'Ubuntu',
'Debian',
'Devuan',
'Arch',
'Arch ARM',
'Manjaro',
'ALT',
'SUSE Enterprise Server',
'SUSE',
'OEL',
'Linaro',
'elementary OS',
'McAfee OS Server',
'Void',
'Mint',
'Raspbian',
'XenServer',
'Cumulus'
))
if __grains__.get('os', '') in disable:
if __grains__.get('os') in disable:
return (False, 'Your OS is on the disabled list')
# Disable on all non-Linux OSes as well
if __grains__['kernel'] != 'Linux':
return (False, 'Non Linux OSes are not supported')
# SUSE >=12.0 uses systemd
if __grains__.get('os_family', '') == 'Suse':
try:
# osrelease might be in decimal format (e.g. "12.1"), or for
# SLES might include service pack (e.g. "11 SP3"), so split on
# non-digit characters, and the zeroth element is the major
# number (it'd be so much simpler if it was always "X.Y"...)
import re
if int(re.split(r'\D+', __grains__.get('osrelease', ''))[0]) >= 12:
return (False, 'SUSE version greater than or equal to 12 is not supported')
except ValueError:
return (False, 'You are missing the os_family grain')
init_grain = __grains__.get('init')
if init_grain not in (None, 'sysvinit', 'unknown'):
return (False, 'Minion is running {0}'.format(init_grain))
elif __utils__['systemd.booted'](__context__):
# Should have been caught by init grain check, but check just in case
return (False, 'Minion is running systemd')
return 'service'

View file

@ -466,18 +466,13 @@ def _get_reg_software():
'{0}\\{1}'.format(key, reg_key),
'DisplayVersion',
use_32bit)
if (not d_vers_regdata['success'] or
d_vers_regdata['vtype'] not in ['REG_SZ', 'REG_EXPAND_SZ', 'REG_DWORD'] or
d_vers_regdata['vdata'] in [None, False]):
return
if isinstance(d_vers_regdata['vdata'], int):
d_vers = six.text_type(d_vers_regdata['vdata'])
else:
d_vers = d_vers_regdata['vdata']
if not d_vers or d_vers == '(value not set)':
d_vers = 'Not Found'
d_vers = 'Not Found'
if (d_vers_regdata['success'] and
d_vers_regdata['vtype'] in ['REG_SZ', 'REG_EXPAND_SZ', 'REG_DWORD']):
if isinstance(d_vers_regdata['vdata'], int):
d_vers = six.text_type(d_vers_regdata['vdata'])
elif d_vers_regdata['vdata'] and d_vers_regdata['vdata'] != '(value not set)': # Check for blank values
d_vers = d_vers_regdata['vdata']
check_ok = False
for check_reg in ['UninstallString', 'QuietUninstallString', 'ModifyPath']:
@ -1097,7 +1092,8 @@ def install(name=None, refresh=False, pkgs=None, **kwargs):
.. versionadded:: 2016.11.0
Returns:
dict: Return a dict containing the new package names and versions
dict: Return a dict containing the new package names and versions. If
the package is already installed, an empty dict is returned.
If the package is installed by ``pkg.install``:
@ -1106,12 +1102,6 @@ def install(name=None, refresh=False, pkgs=None, **kwargs):
{'<package>': {'old': '<old-version>',
'new': '<new-version>'}}
If the package is already installed:
.. code-block:: cfg
{'<package>': {'current': '<current-version>'}}
The following example will refresh the winrepo and install a single
package, 7zip.
@ -1205,7 +1195,6 @@ def install(name=None, refresh=False, pkgs=None, **kwargs):
# Loop through each package
changed = []
latest = []
for pkg_name, options in six.iteritems(pkg_params):
# Load package information for the package
@ -1217,28 +1206,33 @@ def install(name=None, refresh=False, pkgs=None, **kwargs):
ret[pkg_name] = 'Unable to locate package {0}'.format(pkg_name)
continue
version_num = options.get('version', '')
version_num = options.get('version')
# Using the salt cmdline with version=5.3 might be interpreted
# as a float it must be converted to a string in order for
# string matching to work.
if not isinstance(version_num, six.string_types) and version_num is not None:
version_num = six.text_type(version_num)
# If the version was not passed, version_num will be None
if not version_num:
if pkg_name in old:
log.debug('A version (%s) already installed for package '
'%s', version_num, pkg_name)
log.debug('pkg.install: \'%s\' version \'%s\' is already installed',
pkg_name, old[pkg_name][0])
continue
# following can be version number or latest or Not Found
# Get the most recent version number available from winrepo.p
# May also return `latest` or an empty string
version_num = _get_latest_pkg_version(pkginfo)
if version_num == 'latest' and 'latest' not in pkginfo:
# Get the most recent version number available from winrepo.p
# May also return `latest` or an empty string
version_num = _get_latest_pkg_version(pkginfo)
# Check if the version is already installed
if version_num in old.get(pkg_name, []):
# Desired version number already installed
ret[pkg_name] = {'current': version_num}
log.debug('pkg.install: \'%s\' version \'%s\' is already installed',
pkg_name, version_num)
continue
# If version number not installed, is the version available?
elif version_num != 'latest' and version_num not in pkginfo:
@ -1247,9 +1241,6 @@ def install(name=None, refresh=False, pkgs=None, **kwargs):
ret[pkg_name] = {'not found': version_num}
continue
if 'latest' in pkginfo:
latest.append(pkg_name)
# Get the installer settings from winrepo.p
installer = pkginfo[version_num].get('installer', '')
cache_dir = pkginfo[version_num].get('cache_dir', False)
@ -1334,18 +1325,18 @@ def install(name=None, refresh=False, pkgs=None, **kwargs):
# Fix non-windows slashes
cached_pkg = cached_pkg.replace('/', '\\')
cache_path, _ = os.path.split(cached_pkg)
cache_path = os.path.dirname(cached_pkg)
# Compare the hash sums
source_hash = pkginfo[version_num].get('source_hash', False)
if source_hash:
source_sum = _get_source_sum(source_hash, cached_pkg, saltenv)
log.debug('Source %s hash: %s',
log.debug('pkg.install: Source %s hash: %s',
source_sum['hash_type'], source_sum['hsum'])
cached_pkg_sum = salt.utils.hashutils.get_hash(cached_pkg,
source_sum['hash_type'])
log.debug('Package %s hash: %s',
log.debug('pkg.install: Package %s hash: %s',
source_sum['hash_type'], cached_pkg_sum)
if source_sum['hsum'] != cached_pkg_sum:
@ -1353,7 +1344,7 @@ def install(name=None, refresh=False, pkgs=None, **kwargs):
("Source hash '{0}' does not match package hash"
" '{1}'").format(source_sum['hsum'], cached_pkg_sum)
)
log.debug('Source hash matches package hash.')
log.debug('pkg.install: Source hash matches package hash.')
# Get install flags
@ -1464,15 +1455,6 @@ def install(name=None, refresh=False, pkgs=None, **kwargs):
# preparation for the comparison below.
__salt__['pkg_resource.stringify'](old)
# For installers that have no specific version (ie: chrome)
# The software definition file will have a version of 'latest'
# In that case there's no way to know which version has been installed
# Just return the current installed version
if latest:
for pkg_name in latest:
if old.get(pkg_name, 'old') == new.get(pkg_name, 'new'):
ret[pkg_name] = {'current': new[pkg_name]}
# Check for changes in the registry
difference = salt.utils.data.compare_dicts(old, new)

View file

@ -1864,6 +1864,15 @@ def add_trigger(name=None,
- SessionLock: When the workstation is locked
- SessionUnlock: When the workstation is unlocked
.. note::
Arguments are parsed by the YAML loader and are subject to yaml's
idiosyncrasies. Therefore, time values in some formats (``%H:%M:%S`` and
``%H:%M``) should to be quoted. See `YAML IDIOSYNCRASIES`_ for more details.
.. _`YAML IDIOSYNCRASIES`: https://docs.saltstack.com/en/latest/topics/troubleshooting/yaml_idiosyncrasies.html#time-expressions
:return: True if successful, False if unsuccessful
:rtype: bool
@ -1871,7 +1880,7 @@ def add_trigger(name=None,
.. code-block:: bash
salt 'minion-id' task.add_trigger <task_name> trigger_type=Once trigger_enabled=True start_date=2016/12/1 start_time=12:01
salt 'minion-id' task.add_trigger <task_name> trigger_type=Once trigger_enabled=True start_date=2016/12/1 start_time='"12:01"'
'''
if not trigger_type:
return 'Required parameter "trigger_type" not specified'

View file

@ -206,6 +206,11 @@ def mounted(name,
if __grains__['os'] in ['MacOS', 'Darwin'] and opts == 'defaults':
opts = 'noowners'
# Defaults is not a valid option on AIX
if __grains__['os'] in ['AIX']:
if opts == 'defaults':
opts = ''
# Make sure that opts is correct, it can be a list or a comma delimited
# string
if isinstance(opts, string_types):
@ -571,9 +576,14 @@ def mounted(name,
ret['comment'] = '{0} not mounted'.format(name)
if persist:
# Override default for Mac OS
if __grains__['os'] in ['MacOS', 'Darwin'] and config == '/etc/fstab':
config = "/etc/auto_salt"
if '/etc/fstab' == config:
# Override default for Mac OS
if __grains__['os'] in ['MacOS', 'Darwin']:
config = "/etc/auto_salt"
# Override default for AIX
elif 'AIX' in __grains__['os']:
config = "/etc/filesystems"
if __opts__['test']:
if __grains__['os'] in ['MacOS', 'Darwin']:
@ -583,6 +593,15 @@ def mounted(name,
opts,
config,
test=True)
elif __grains__['os'] in ['AIX']:
out = __salt__['mount.set_filesystems'](name,
device,
fstype,
opts,
mount,
config,
test=True,
match_on=match_on)
else:
out = __salt__['mount.set_fstab'](name,
device,
@ -631,6 +650,14 @@ def mounted(name,
fstype,
opts,
config)
elif __grains__['os'] in ['AIX']:
out = __salt__['mount.set_filesystems'](name,
device,
fstype,
opts,
mount,
config,
match_on=match_on)
else:
out = __salt__['mount.set_fstab'](name,
device,
@ -712,7 +739,15 @@ def swap(name, persist=True, config='/etc/fstab'):
ret['result'] = False
if persist:
fstab_data = __salt__['mount.fstab'](config)
device_key_name = 'device'
if 'AIX' in __grains__['os']:
device_key_name = 'dev'
if '/etc/fstab' == config:
# Override default for AIX
config = "/etc/filesystems"
fstab_data = __salt__['mount.filesystems'](config)
else:
fstab_data = __salt__['mount.fstab'](config)
if __opts__['test']:
if name not in fstab_data:
ret['result'] = None
@ -722,19 +757,25 @@ def swap(name, persist=True, config='/etc/fstab'):
return ret
if 'none' in fstab_data:
if fstab_data['none']['device'] == name and \
if fstab_data['none'][device_key_name] == name and \
fstab_data['none']['fstype'] != 'swap':
return ret
# present, new, change, bad config
# Make sure the entry is in the fstab
out = __salt__['mount.set_fstab']('none',
name,
'swap',
['defaults'],
0,
0,
config)
if 'AIX' in __grains__['os']:
out = None
ret['result'] = False
ret['comment'] += '. swap not present in /etc/filesystems on AIX.'
return ret
else:
# present, new, change, bad config
# Make sure the entry is in the fstab
out = __salt__['mount.set_fstab']('none',
name,
'swap',
['defaults'],
0,
0,
config)
if out == 'present':
return ret
if out == 'new':
@ -823,10 +864,16 @@ def unmounted(name,
cache_result = __salt__['mount.delete_mount_cache'](name)
if persist:
device_key_name = 'device'
# Override default for Mac OS
if __grains__['os'] in ['MacOS', 'Darwin'] and config == '/etc/fstab':
config = "/etc/auto_salt"
fstab_data = __salt__['mount.automaster'](config)
elif 'AIX' in __grains__['os']:
device_key_name = 'dev'
if config == '/etc/fstab':
config = "/etc/filesystems"
fstab_data = __salt__['mount.filesystems'](config)
else:
fstab_data = __salt__['mount.fstab'](config)
@ -834,7 +881,7 @@ def unmounted(name,
ret['comment'] += '. fstab entry not found'
else:
if device:
if fstab_data[name]['device'] != device:
if fstab_data[name][device_key_name] != device:
ret['comment'] += '. fstab entry for device {0} not found'.format(device)
return ret
if __opts__['test']:
@ -846,6 +893,8 @@ def unmounted(name,
else:
if __grains__['os'] in ['MacOS', 'Darwin']:
out = __salt__['mount.rm_automaster'](name, device, config)
elif 'AIX' in __grains__['os']:
out = __salt__['mount.rm_filesystems'](name, device, config)
else:
out = __salt__['mount.rm_fstab'](name, device, config)
if out is not True:

View file

@ -407,7 +407,10 @@ def flopen(*args, **kwargs):
with fopen(*args, **kwargs) as f_handle:
try:
if is_fcntl_available(check_sunos=True):
fcntl.flock(f_handle.fileno(), fcntl.LOCK_SH)
lock_type = fcntl.LOCK_SH
if 'w' in args[1] or 'a' in args[1]:
lock_type = fcntl.LOCK_EX
fcntl.flock(f_handle.fileno(), lock_type)
yield f_handle
finally:
if is_fcntl_available(check_sunos=True):

View file

@ -36,6 +36,7 @@ import salt.utils.zeromq
from salt._compat import ipaddress
from salt.exceptions import SaltClientError, SaltSystemExit
from salt.utils.decorators.jinja import jinja_filter
from salt.utils.versions import LooseVersion
# inet_pton does not exist in Windows, this is a workaround
if salt.utils.platform.is_windows():
@ -865,6 +866,85 @@ def linux_interfaces():
return ifaces
def _netbsd_interfaces_ifconfig(out):
'''
Uses ifconfig to return a dictionary of interfaces with various information
about each (up/down state, ip address, netmask, and hwaddr)
'''
ret = dict()
piface = re.compile(r'^([^\s:]+)')
pmac = re.compile('.*?address: ([0-9a-f:]+)')
pip = re.compile(r'.*?inet [^\d]*(.*?)/([\d]*)\s')
pip6 = re.compile(r'.*?inet6 ([0-9a-f:]+)%([a-zA-Z0-9]*)/([\d]*)\s')
pupdown = re.compile('UP')
pbcast = re.compile(r'.*?broadcast ([\d\.]+)')
groups = re.compile('\r?\n(?=\\S)').split(out)
for group in groups:
data = dict()
iface = ''
updown = False
for line in group.splitlines():
miface = piface.match(line)
mmac = pmac.match(line)
mip = pip.match(line)
mip6 = pip6.match(line)
mupdown = pupdown.search(line)
if miface:
iface = miface.group(1)
if mmac:
data['hwaddr'] = mmac.group(1)
if mip:
if 'inet' not in data:
data['inet'] = list()
addr_obj = dict()
addr_obj['address'] = mip.group(1)
mmask = mip.group(2)
if mip.group(2):
addr_obj['netmask'] = cidr_to_ipv4_netmask(mip.group(2))
mbcast = pbcast.match(line)
if mbcast:
addr_obj['broadcast'] = mbcast.group(1)
data['inet'].append(addr_obj)
if mupdown:
updown = True
if mip6:
if 'inet6' not in data:
data['inet6'] = list()
addr_obj = dict()
addr_obj['address'] = mip6.group(1)
mmask6 = mip6.group(3)
addr_obj['scope'] = mip6.group(2)
addr_obj['prefixlen'] = mip6.group(3)
data['inet6'].append(addr_obj)
data['up'] = updown
ret[iface] = data
del data
return ret
def netbsd_interfaces():
'''
Obtain interface information for NetBSD >= 8 where the ifconfig
output diverged from other BSD variants (Netmask is now part of the
address)
'''
# NetBSD versions prior to 8.0 can still use linux_interfaces()
if LooseVersion(os.uname()[2]) < LooseVersion('8.0'):
return linux_interfaces()
ifconfig_path = salt.utils.path.which('ifconfig')
cmd = subprocess.Popen(
'{0} -a'.format(ifconfig_path),
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT).communicate()[0]
return _netbsd_interfaces_ifconfig(salt.utils.stringutils.to_str(cmd))
def _interfaces_ipconfig(out):
'''
Returns a dictionary of interfaces with various information about each
@ -966,6 +1046,8 @@ def interfaces():
'''
if salt.utils.platform.is_windows():
return win_interfaces()
elif salt.utils.platform.is_netbsd():
return netbsd_interfaces()
else:
return linux_interfaces()

View file

@ -175,7 +175,7 @@ def enable_ctrl_logoff_handler():
)
def escape_argument(arg):
def escape_argument(arg, escape=True):
'''
Escape the argument for the cmd.exe shell.
See http://blogs.msdn.com/b/twistylittlepassagesallalike/archive/2011/04/23/everyone-quotes-arguments-the-wrong-way.aspx
@ -186,12 +186,19 @@ def escape_argument(arg):
Args:
arg (str): a single command line argument to escape for the cmd.exe shell
Kwargs:
escape (bool): True will call the escape_for_cmd_exe() function
which escapes the characters '()%!^"<>&|'. False
will not call the function and only quotes the cmd
Returns:
str: an escaped string suitable to be passed as a program argument to the cmd.exe shell
'''
if not arg or re.search(r'(["\s])', arg):
arg = '"' + arg.replace('"', r'\"') + '"'
if not escape:
return arg
return escape_for_cmd_exe(arg)

View file

@ -21,6 +21,7 @@ import salt.utils.files
# Import Salt Testing Libs
from tests.support.case import ShellCase, SSHCase
from tests.support.helpers import flaky
class GrainsTargetingTest(ShellCase):
@ -52,6 +53,7 @@ class GrainsTargetingTest(ShellCase):
sub_minion = self.run_salt('-G \'id:sub_minion\' test.ping')
self.assertEqual(sorted(sub_minion), sorted(['sub_minion:', ' True']))
@flaky
def test_grains_targeting_disconnected(self):
'''
Tests return of minion using grains targeting on a disconnected minion.
@ -72,7 +74,7 @@ class GrainsTargetingTest(ShellCase):
for item in self.run_salt('-t 1 -G \'id:disconnected\' test.ping', timeout=40):
if item != 'disconnected:':
ret = item.strip()
self.assertEqual(ret, test_ret)
assert ret == test_ret
finally:
os.unlink(key_file)

View file

@ -104,8 +104,8 @@ autosign_file: autosign_file
discovery: false
# enable using ssh minions and regular minions
enable_ssh_minions: True
ignore_host_keys: True
#enable_ssh_minions: True
#ignore_host_keys: True
# test vault
vault:

View file

@ -84,7 +84,7 @@ class CMDModuleTest(ModuleCase):
'''
self.assertEqual(self.run_function('cmd.run_stdout',
['echo "cheese"']).rstrip(),
'cheese')
'cheese' if not salt.utils.platform.is_windows() else '"cheese"')
def test_stderr(self):
'''
@ -99,7 +99,7 @@ class CMDModuleTest(ModuleCase):
['echo "cheese" 1>&2',
'shell={0}'.format(shell)], python_shell=True
).rstrip(),
'cheese')
'cheese' if not salt.utils.platform.is_windows() else '"cheese"')
def test_run_all(self):
'''
@ -120,7 +120,7 @@ class CMDModuleTest(ModuleCase):
self.assertTrue(isinstance(ret.get('retcode'), int))
self.assertTrue(isinstance(ret.get('stdout'), six.string_types))
self.assertTrue(isinstance(ret.get('stderr'), six.string_types))
self.assertEqual(ret.get('stderr').rstrip(), 'cheese')
self.assertEqual(ret.get('stderr').rstrip(), 'cheese' if not salt.utils.platform.is_windows() else '"cheese"')
def test_retcode(self):
'''
@ -281,15 +281,21 @@ class CMDModuleTest(ModuleCase):
'''
cmd = '''echo 'SELECT * FROM foo WHERE bar="baz"' '''
expected_result = 'SELECT * FROM foo WHERE bar="baz"'
if salt.utils.platform.is_windows():
expected_result = '\'SELECT * FROM foo WHERE bar="baz"\''
result = self.run_function('cmd.run_stdout', [cmd]).strip()
self.assertEqual(result, expected_result)
@skip_if_not_root
@skipIf(salt.utils.platform.is_windows, 'skip windows, requires password')
def test_quotes_runas(self):
'''
cmd.run with quoted command
'''
cmd = '''echo 'SELECT * FROM foo WHERE bar="baz"' '''
if salt.utils.platform.is_darwin():
cmd = '''echo 'SELECT * FROM foo WHERE bar=\\"baz\\"' '''
expected_result = 'SELECT * FROM foo WHERE bar="baz"'
runas = this_user()
@ -312,6 +318,7 @@ class CMDModuleTest(ModuleCase):
out = self.run_function('cmd.run', ['env'], runas=self.runas_usr).splitlines()
self.assertIn('USER={0}'.format(self.runas_usr), out)
@skipIf(not salt.utils.path.which_bin('sleep'), 'sleep cmd not installed')
def test_timeout(self):
'''
cmd.run trigger timeout
@ -322,6 +329,7 @@ class CMDModuleTest(ModuleCase):
python_shell=True)
self.assertTrue('Timed out' in out)
@skipIf(not salt.utils.path.which_bin('sleep'), 'sleep cmd not installed')
def test_timeout_success(self):
'''
cmd.run sufficient timeout to succeed

View file

@ -129,6 +129,11 @@ class ServiceModuleTest(ModuleCase):
if tuple(self.run_function('grains.item', ['osrelease_info'])['osrelease_info']) == (14, 0o4) and not systemd:
# currently upstart does not have a mechanism to report if disabling a service fails if does not exist
self.assertTrue(self.run_function('service.disable', [srv_name]))
elif self.run_function('grains.item', ['osfullname'])['osfullname'] == 'Debian' and \
self.run_function('grains.item', ['osmajorrelease'])['osmajorrelease'] < 9 and systemd:
# currently disabling a service via systemd that does not exist
# on Debian 8 results in a True return code
self.assertTrue(self.run_function('service.disable', [srv_name]))
else:
try:
disable = self.run_function('service.disable', [srv_name])

View file

@ -80,6 +80,7 @@ class TestSaltAPIHandler(_SaltnadoIntegrationTestCase):
self.assertEqual(response.headers['Location'], '/login')
# Local client tests
@skipIf(True, 'to be re-enabled when #23623 is merged')
def test_simple_local_post(self):
'''
@ -121,6 +122,7 @@ class TestSaltAPIHandler(_SaltnadoIntegrationTestCase):
self.assertEqual(response_obj['return'], ["No minions matched the target. No command was sent, no jid was assigned."])
# local client request body test
@skipIf(True, 'Undetermined race condition in test. Temporarily disabled.')
def test_simple_local_post_only_dictionary_request(self):
'''
@ -177,7 +179,7 @@ class TestSaltAPIHandler(_SaltnadoIntegrationTestCase):
# TODO: verify pub function? Maybe look at how we test the publisher
self.assertEqual(len(ret), 1)
self.assertIn('jid', ret[0])
self.assertEqual(ret[0]['minions'], sorted(['minion', 'sub_minion', 'localhost']))
self.assertEqual(ret[0]['minions'], sorted(['minion', 'sub_minion']))
def test_multi_local_async_post(self):
low = [{'client': 'local_async',
@ -203,8 +205,8 @@ class TestSaltAPIHandler(_SaltnadoIntegrationTestCase):
self.assertEqual(len(ret), 2)
self.assertIn('jid', ret[0])
self.assertIn('jid', ret[1])
self.assertEqual(ret[0]['minions'], sorted(['minion', 'sub_minion', 'localhost']))
self.assertEqual(ret[1]['minions'], sorted(['minion', 'sub_minion', 'localhost']))
self.assertEqual(ret[0]['minions'], sorted(['minion', 'sub_minion']))
self.assertEqual(ret[1]['minions'], sorted(['minion', 'sub_minion']))
def test_multi_local_async_post_multitoken(self):
low = [{'client': 'local_async',
@ -238,8 +240,8 @@ class TestSaltAPIHandler(_SaltnadoIntegrationTestCase):
self.assertIn('jid', ret[0]) # the first 2 are regular returns
self.assertIn('jid', ret[1])
self.assertIn('Authentication error occurred.', ret[2]) # bad auth
self.assertEqual(ret[0]['minions'], sorted(['minion', 'sub_minion', 'localhost']))
self.assertEqual(ret[1]['minions'], sorted(['minion', 'sub_minion', 'localhost']))
self.assertEqual(ret[0]['minions'], sorted(['minion', 'sub_minion']))
self.assertEqual(ret[1]['minions'], sorted(['minion', 'sub_minion']))
def test_simple_local_async_post_no_tgt(self):
low = [{'client': 'local_async',
@ -294,7 +296,7 @@ class TestSaltAPIHandler(_SaltnadoIntegrationTestCase):
)
response_obj = salt.utils.json.loads(response.body)
self.assertEqual(len(response_obj['return']), 1)
self.assertEqual(sorted(response_obj['return'][0]), sorted(['localhost', 'minion', 'sub_minion']))
self.assertEqual(sorted(response_obj['return'][0]), sorted(['minion', 'sub_minion']))
# runner_async tests
def test_simple_local_runner_async_post(self):

View file

@ -35,7 +35,7 @@ class NetapiClientTest(TestCase):
low.update(self.eauth_creds)
ret = self.netapi.run(low)
self.assertEqual(ret, {'minion': True, 'sub_minion': True, 'localhost': True})
self.assertEqual(ret, {'minion': True, 'sub_minion': True})
def test_local_batch(self):
low = {'client': 'local_batch', 'tgt': '*', 'fun': 'test.ping'}
@ -45,7 +45,6 @@ class NetapiClientTest(TestCase):
rets = []
for _ret in ret:
rets.append(_ret)
self.assertIn({'localhost': True}, rets)
self.assertIn({'sub_minion': True}, rets)
self.assertIn({'minion': True}, rets)
@ -59,7 +58,7 @@ class NetapiClientTest(TestCase):
self.assertIn('jid', ret)
ret.pop('jid', None)
ret['minions'] = sorted(ret['minions'])
self.assertEqual(ret, {'minions': sorted(['minion', 'sub_minion', 'localhost'])})
self.assertEqual(ret, {'minions': sorted(['minion', 'sub_minion'])})
def test_wheel(self):
low = {'client': 'wheel', 'fun': 'key.list_all'}

View file

@ -17,10 +17,10 @@ import threading
# Import Salt Testing Libs
from tests.support.case import ShellCase
from tests.support.unit import skipIf
from tests.support.paths import TMP
from tests.support.helpers import flaky, expensiveTest
from tests.support.mock import MagicMock, patch
from tests.support.paths import TMP
from tests.support.unit import skipIf
# Import Salt Libs
import salt.exceptions
@ -73,11 +73,11 @@ class StateRunnerTest(ShellCase):
# First, check that we don't have the "bad" output that was displaying in
# Issue #31330 where only the highstate outputter was listed
self.assertIsNot(bad_out, ret_output)
assert bad_out != ret_output
# Now test that some expected good sample output is present in the return.
for item in good_out:
self.assertIn(item, ret_output)
assert item in ret_output
def test_orchestrate_nested(self):
'''
@ -90,8 +90,8 @@ class StateRunnerTest(ShellCase):
'state.orchestrate nested-orch.outer',
with_retcode=True)
self.assertFalse(os.path.exists('/tmp/ewu-2016-12-13'))
self.assertNotEqual(code, 0)
assert os.path.exists('/tmp/ewu-2016-12-13') is False
assert code != 0
def test_orchestrate_state_and_function_failure(self):
'''
@ -168,7 +168,7 @@ class StateRunnerTest(ShellCase):
for out in ret_out:
for item in out:
self.assertIn(item, ret)
assert item in ret
def test_orchestrate_retcode(self):
'''
@ -199,7 +199,7 @@ class StateRunnerTest(ShellCase):
' Result: False'):
self.assertIn(result, ret)
def test_orchestrate_target_doesnt_exists(self):
def test_orchestrate_target_doesnt_exist(self):
'''
test orchestration when target doesn't exist
while using multiple states
@ -223,7 +223,7 @@ class StateRunnerTest(ShellCase):
for out in ret_out:
for item in out:
self.assertIn(item, ret)
assert item in ret
def test_state_event(self):
'''
@ -241,7 +241,7 @@ class StateRunnerTest(ShellCase):
while q.empty():
self.run_salt('minion test.ping --static')
out = q.get()
self.assertIn(expect, six.text_type(out))
assert expect in six.text_type(out)
server_thread.join()
@ -254,9 +254,9 @@ class StateRunnerTest(ShellCase):
def count(thing, listobj):
return sum([obj.strip() == thing for obj in listobj])
self.assertEqual(count('ID: test subset', ret), 1)
self.assertEqual(count('Succeeded: 1', ret), 1)
self.assertEqual(count('Failed: 0', ret), 1)
assert count('ID: test subset', ret) == 1
assert count('Succeeded: 1', ret) == 1
assert count('Failed: 0', ret) == 1
def test_orchestrate_salt_function_return_false_failure(self):
'''
@ -275,10 +275,7 @@ class StateRunnerTest(ShellCase):
state_result = ret['data']['master']['salt_|-deploy_check_|-test.false_|-function']['result']
func_ret = ret['data']['master']['salt_|-deploy_check_|-test.false_|-function']['changes']
self.assertEqual(
state_result,
False,
)
assert state_result is False
self.assertEqual(
func_ret,
@ -397,7 +394,7 @@ class OrchEventTest(ShellCase):
del listener
signal.alarm(0)
@skipIf(True, 'This test causes problems on the test suite sometimes.')
@expensiveTest
def test_parallel_orchestrations(self):
'''
Test to confirm that the parallel state requisite works in orch

View file

@ -556,3 +556,34 @@ class SchedulerEvalTest(ModuleCase, SaltReturnAssertsMixin):
self.schedule.eval(now=run_time)
ret = self.schedule.job_status('job_eval_splay')
self.assertEqual(ret['_last_run'], run_time)
def test_eval_enabled(self):
'''
verify that scheduled job runs
when the enabled key is in place
https://github.com/saltstack/salt/issues/47695
'''
job = {
'schedule': {
'enabled': True,
'job1': {
'function': 'test.ping',
'when': '11/29/2017 4:00pm',
}
}
}
run_time2 = dateutil_parser.parse('11/29/2017 4:00pm')
run_time1 = run_time2 - datetime.timedelta(seconds=1)
# Add the job to the scheduler
self.schedule.opts.update(job)
# Evaluate 1 second before the run time
self.schedule.eval(now=run_time1)
ret = self.schedule.job_status('job1')
self.assertNotIn('_last_run', ret)
# Evaluate 1 second at the run time
self.schedule.eval(now=run_time2)
ret = self.schedule.job_status('job1')
self.assertEqual(ret['_last_run'], run_time2)

View file

@ -22,7 +22,7 @@ from tests.support.case import ShellCase
from tests.support.unit import skipIf
from tests.support.paths import FILES, TMP
from tests.support.mixins import ShellCaseCommonTestsMixin
from tests.support.helpers import destructiveTest
from tests.support.helpers import destructiveTest, flaky
from tests.integration.utils import testprogram
# Import salt libs
@ -103,12 +103,10 @@ class CallTest(ShellCase, testprogram.TestProgramCase, ShellCaseCommonTestsMixin
log.debug('The pkg: {0} is already installed on the machine'.format(pkg))
@skipIf(sys.platform.startswith('win'), 'This test does not apply on Win')
@flaky
def test_user_delete_kw_output(self):
ret = self.run_call('-l quiet -d user.delete')
self.assertIn(
'salt \'*\' user.delete name remove=True force=True',
''.join(ret)
)
assert 'salt \'*\' user.delete name remove=True force=True' in ''.join(ret)
def test_salt_documentation_too_many_arguments(self):
'''

View file

@ -8,8 +8,9 @@ import time
# Import Salt Testing libs
from tests.support.case import ShellCase
from tests.support.paths import TMP
from tests.support.helpers import flaky
from tests.support.mixins import ShellCaseCommonTestsMixin
from tests.support.paths import TMP
# Import salt libs
import salt.utils.files
@ -91,11 +92,13 @@ class MatchTest(ShellCase, ShellCaseCommonTestsMixin):
assert minion_in_returns('minion', data) is True
assert minion_in_returns('sub_minion', data) is True
@flaky
def test_compound_pillar(self):
data = self.run_salt("-C 'I%@companions%three%sarah*' test.ping")
assert minion_in_returns('minion', data) is True
assert minion_in_returns('sub_minion', data) is True
@flaky
def test_coumpound_pillar_pcre(self):
data = self.run_salt("-C 'J%@knights%^(Lancelot|Galahad)$' test.ping")
self.assertTrue(minion_in_returns('minion', data))
@ -335,6 +338,7 @@ class MatchTest(ShellCase, ShellCaseCommonTestsMixin):
data = self.run_salt('-d "*" user')
self.assertIn('user.add:', data)
@flaky
def test_salt_documentation_arguments_not_assumed(self):
'''
Test to see if we're not auto-adding '*' and 'sys.doc' to the call
@ -346,16 +350,16 @@ class MatchTest(ShellCase, ShellCaseCommonTestsMixin):
'see https://github.com/saltstack/salt-jenkins/issues/324.')
data = self.run_salt('-d -t 20')
if data:
self.assertIn('user.add:', data)
assert 'user.add:' in data
data = self.run_salt('"*" -d -t 20')
if data:
self.assertIn('user.add:', data)
assert 'user.add:' in data
data = self.run_salt('"*" -d user -t 20')
self.assertIn('user.add:', data)
assert 'user.add:' in data
data = self.run_salt('"*" sys.doc -d user -t 20')
self.assertIn('user.add:', data)
assert 'user.add:' in data
data = self.run_salt('"*" sys.doc user -t 20')
self.assertIn('user.add:', data)
assert 'user.add:' in data
def test_salt_documentation_too_many_arguments(self):
'''

View file

@ -1085,6 +1085,37 @@ class FileTest(ModuleCase, SaltReturnAssertsMixin):
self.assertTrue(os.path.exists(good_file))
self.assertFalse(os.path.exists(wrong_file))
def test_directory_broken_symlink(self):
'''
Ensure that file.directory works even if a directory
contains broken symbolic link
'''
try:
tmp_dir = os.path.join(TMP, 'foo')
null_file = '{0}/null'.format(tmp_dir)
broken_link = '{0}/broken'.format(tmp_dir)
if IS_WINDOWS:
self.run_function('file.mkdir', [tmp_dir, 'Administrators'])
else:
os.mkdir(tmp_dir, 0o700)
self.run_function('file.symlink', [null_file, broken_link])
if IS_WINDOWS:
ret = self.run_state(
'file.directory', name=tmp_dir, recurse={'mode'},
follow_symlinks=True, win_owner='Administrators')
else:
ret = self.run_state(
'file.directory', name=tmp_dir, recurse={'mode'},
file_mode=644, dir_mode=755)
self.assertSaltTrueReturn(ret)
finally:
if os.path.isdir(tmp_dir):
self.run_function('file.remove', [tmp_dir])
@with_tempdir(create=False)
def test_recurse(self, name):
'''
@ -2564,8 +2595,8 @@ class BlockreplaceTest(ModuleCase, SaltReturnAssertsMixin):
Test blockreplace when prepend_if_not_found=True and block doesn't
exist in file.
'''
expected = self.marker_start + os.linesep + self.content + \
self.marker_end + os.linesep + self.without_block
expected = self.marker_start + '\n' + self.content + \
self.marker_end + '\n' + self.without_block
# Pass 1: content ends in newline
self._write(name, self.without_block)
@ -2618,8 +2649,8 @@ class BlockreplaceTest(ModuleCase, SaltReturnAssertsMixin):
exist in file. Test with append_newline explicitly set to True.
'''
# Pass 1: content ends in newline
expected = self.marker_start + os.linesep + self.content + \
os.linesep + self.marker_end + os.linesep + self.without_block
expected = self.marker_start + '\n' + self.content + \
'\n' + self.marker_end + '\n' + self.without_block
self._write(name, self.without_block)
ret = self.run_state('file.blockreplace',
name=name,
@ -2644,8 +2675,8 @@ class BlockreplaceTest(ModuleCase, SaltReturnAssertsMixin):
self.assertEqual(self._read(name), expected)
# Pass 2: content does not end in newline
expected = self.marker_start + os.linesep + self.content + \
self.marker_end + os.linesep + self.without_block
expected = self.marker_start + '\n' + self.content + \
self.marker_end + '\n' + self.without_block
self._write(name, self.without_block)
ret = self.run_state('file.blockreplace',
name=name,
@ -2676,8 +2707,8 @@ class BlockreplaceTest(ModuleCase, SaltReturnAssertsMixin):
exist in file. Test with append_newline explicitly set to False.
'''
# Pass 1: content ends in newline
expected = self.marker_start + os.linesep + self.content + \
self.marker_end + os.linesep + self.without_block
expected = self.marker_start + '\n' + self.content + \
self.marker_end + '\n' + self.without_block
self._write(name, self.without_block)
ret = self.run_state('file.blockreplace',
name=name,
@ -2702,8 +2733,8 @@ class BlockreplaceTest(ModuleCase, SaltReturnAssertsMixin):
self.assertEqual(self._read(name), expected)
# Pass 2: content does not end in newline
expected = self.marker_start + os.linesep + \
self.content.rstrip('\r\n') + self.marker_end + os.linesep + \
expected = self.marker_start + '\n' + \
self.content.rstrip('\r\n') + self.marker_end + '\n' + \
self.without_block
self._write(name, self.without_block)
ret = self.run_state('file.blockreplace',
@ -2734,8 +2765,8 @@ class BlockreplaceTest(ModuleCase, SaltReturnAssertsMixin):
Test blockreplace when append_if_not_found=True and block doesn't
exist in file.
'''
expected = self.without_block + self.marker_start + os.linesep + \
self.content + self.marker_end + os.linesep
expected = self.without_block + self.marker_start + '\n' + \
self.content + self.marker_end + '\n'
# Pass 1: content ends in newline
self._write(name, self.without_block)
@ -2788,8 +2819,8 @@ class BlockreplaceTest(ModuleCase, SaltReturnAssertsMixin):
exist in file. Test with append_newline explicitly set to True.
'''
# Pass 1: content ends in newline
expected = self.without_block + self.marker_start + os.linesep + \
self.content + os.linesep + self.marker_end + os.linesep
expected = self.without_block + self.marker_start + '\n' + \
self.content + '\n' + self.marker_end + '\n'
self._write(name, self.without_block)
ret = self.run_state('file.blockreplace',
name=name,
@ -2814,8 +2845,8 @@ class BlockreplaceTest(ModuleCase, SaltReturnAssertsMixin):
self.assertEqual(self._read(name), expected)
# Pass 2: content does not end in newline
expected = self.without_block + self.marker_start + os.linesep + \
self.content + self.marker_end + os.linesep
expected = self.without_block + self.marker_start + '\n' + \
self.content + self.marker_end + '\n'
self._write(name, self.without_block)
ret = self.run_state('file.blockreplace',
name=name,
@ -2846,8 +2877,8 @@ class BlockreplaceTest(ModuleCase, SaltReturnAssertsMixin):
exist in file. Test with append_newline explicitly set to False.
'''
# Pass 1: content ends in newline
expected = self.without_block + self.marker_start + os.linesep + \
self.content + self.marker_end + os.linesep
expected = self.without_block + self.marker_start + '\n' + \
self.content + self.marker_end + '\n'
self._write(name, self.without_block)
ret = self.run_state('file.blockreplace',
name=name,
@ -2872,8 +2903,8 @@ class BlockreplaceTest(ModuleCase, SaltReturnAssertsMixin):
self.assertEqual(self._read(name), expected)
# Pass 2: content does not end in newline
expected = self.without_block + self.marker_start + os.linesep + \
self.content.rstrip('\r\n') + self.marker_end + os.linesep
expected = self.without_block + self.marker_start + '\n' + \
self.content.rstrip('\r\n') + self.marker_end + '\n'
self._write(name, self.without_block)
ret = self.run_state('file.blockreplace',
name=name,

View file

@ -1065,6 +1065,16 @@ class PkgTest(ModuleCase, SaltReturnAssertsMixin):
pkg_targets = _PKG_TARGETS.get(os_family, [])
if os_family.lower() == 'redhat':
# If we're in the Red Hat family first we ensure that
# the yum-plugin-versionlock package is installed
ret = self.run_state(
'pkg.installed',
name='yum-plugin-versionlock',
refresh=False,
)
self.assertSaltTrueReturn(ret)
# Make sure that we have targets that match the os_family. If this
# fails then the _PKG_TARGETS dict above needs to have an entry added,
# with two packages that are not installed before these tests are run
@ -1088,15 +1098,25 @@ class PkgTest(ModuleCase, SaltReturnAssertsMixin):
refresh=False,
)
# changes from pkg.hold for Red Hat family are different
if os_family.lower() == 'redhat':
target_changes = {'new': 'hold', 'old': ''}
elif os_family.lower() == 'debian':
target_changes = {'new': 'hold', 'old': 'install'}
try:
tag = 'pkg_|-{0}_|-{0}_|-installed'.format(target)
self.assertSaltTrueReturn(ret)
self.assertIn(tag, ret)
self.assertIn('changes', ret[tag])
self.assertIn(target, ret[tag]['changes'])
self.assertEqual(ret[tag]['changes'][target], {'new': 'hold', 'old': 'install'})
self.assertEqual(ret[tag]['changes'][target], target_changes)
finally:
# Clean up, unhold package and remove
self.run_function('pkg.unhold', name=target)
ret = self.run_state('pkg.removed', name=target)
self.assertSaltTrueReturn(ret)
if os_family.lower() == 'redhat':
ret = self.run_state('pkg.removed',
name='yum-plugin-versionlock')
self.assertSaltTrueReturn(ret)

View file

@ -995,6 +995,17 @@ class FileModuleTestCase(TestCase, LoaderModuleMockMixin):
baz
яйца
''')
diff_result = textwrap.dedent('''\
--- text1
+++ text2
@@ -1,4 +1,4 @@
foo
bar
baz
-спам
+яйца
''')
# The below two variables are 8 bytes of data pulled from /dev/urandom
binary1 = b'\xd4\xb2\xa6W\xc6\x8e\xf5\x0f'
binary2 = b',\x13\x04\xa5\xb0\x12\xdf%'
@ -1025,7 +1036,7 @@ class FileModuleTestCase(TestCase, LoaderModuleMockMixin):
# pylint: enable=no-self-argument
fopen = MagicMock(side_effect=lambda x, *args, **kwargs: MockFopen(x))
cache_file = MagicMock(side_effect=lambda x, *args, **kwargs: x)
cache_file = MagicMock(side_effect=lambda x, *args, **kwargs: x.split('/')[-1])
# Mocks for __utils__['files.is_text']
mock_text_text = MagicMock(side_effect=[True, True])
@ -1045,19 +1056,18 @@ class FileModuleTestCase(TestCase, LoaderModuleMockMixin):
# Non-identical files
ret = filemod.get_diff('text1', 'text2')
self.assertEqual(
ret,
textwrap.dedent('''\
--- text1
+++ text2
@@ -1,4 +1,4 @@
foo
bar
baz
-спам
+яйца
''')
)
self.assertEqual(ret, diff_result)
# Repeat the above test with remote file paths. The expectation
# is that the cp.cache_file mock will ensure that we are not
# trying to do an fopen on the salt:// URL, but rather the
# "cached" file path we've mocked.
with patch.object(filemod, '_binary_replace',
MagicMock(return_value='')):
ret = filemod.get_diff('salt://text1', 'salt://text1')
self.assertEqual(ret, '')
ret = filemod.get_diff('salt://text1', 'salt://text2')
self.assertEqual(ret, diff_result)
# Test diffing two binary files
with patch.dict(filemod.__utils__, {'files.is_text': mock_bin_bin}):

View file

@ -87,6 +87,11 @@ class MountTestCase(TestCase, LoaderModuleMockMixin):
with patch.object(mount, '_active_mounts_darwin', mock):
self.assertEqual(mount.active(extended=True), {})
with patch.dict(mount.__grains__, {'os': 'AIX', 'kernel': 'AIX'}):
mock = MagicMock(return_value={})
with patch.object(mount, '_active_mounts_aix', mock):
self.assertEqual(mount.active(), {})
def test_fstab(self):
'''
List the content of the fstab
@ -135,6 +140,48 @@ class MountTestCase(TestCase, LoaderModuleMockMixin):
'pass_fsck': '-'}
}, vfstab
def test_filesystems(self):
'''
List the content of the filesystems
'''
file_data = textwrap.dedent('''\
#
''')
mock = MagicMock(return_value=True)
with patch.dict(mount.__grains__, {'os': 'AIX', 'kernel': 'AIX'}), \
patch.object(os.path, 'isfile', mock), \
patch('salt.utils.files.fopen', mock_open(read_data=file_data)):
self.assertEqual(mount.filesystems(), {})
file_data = textwrap.dedent('''\
#
/home:
dev = /dev/hd1
vfs = jfs2
log = /dev/hd8
mount = true
check = true
vol = /home
free = false
quota = no
''')
mock = MagicMock(return_value=True)
with patch.dict(mount.__grains__, {'os': 'AIX', 'kernel': 'AIX'}), \
patch.object(os.path, 'isfile', mock), \
patch('salt.utils.files.fopen', mock_open(read_data=file_data)):
fsyst = mount.filesystems()
test_fsyst = {'/home': {'dev': '/dev/hd1',
'vfs': 'jfs2',
'log': '/dev/hd8',
'mount': 'true',
'check': 'true',
'vol': '/home',
'free': 'false',
'quota': 'no'}}
self.assertEqual(test_fsyst, fsyst)
def test_rm_fstab(self):
'''
Remove the mount point from the fstab
@ -198,6 +245,53 @@ class MountTestCase(TestCase, LoaderModuleMockMixin):
'''
self.assertDictEqual(mount.automaster(), {})
def test_rm_filesystems(self):
'''
Remove the mount point from the filesystems
'''
file_data = textwrap.dedent('''\
#
''')
mock = MagicMock(return_value=True)
with patch.dict(mount.__grains__, {'os': 'AIX', 'kernel': 'AIX'}), \
patch.object(os.path, 'isfile', mock), \
patch('salt.utils.files.fopen', mock_open(read_data=file_data)):
self.assertFalse(mount.rm_filesystems('name', 'device'))
file_data = textwrap.dedent('''\
#
/name:
dev = device
vol = /name
''')
mock = MagicMock(return_value=True)
mock_fsyst = MagicMock(return_value=True)
with patch.dict(mount.__grains__, {'os': 'AIX', 'kernel': 'AIX'}), \
patch.object(os.path, 'isfile', mock), \
patch('salt.utils.files.fopen', mock_open(read_data=file_data)):
self.assertTrue(mount.rm_filesystems('/name', 'device'))
def test_set_filesystems(self):
'''
Tests to verify that this mount is represented in the filesystems,
change the mount to match the data passed, or add the mount
if it is not present.
'''
mock = MagicMock(return_value=False)
with patch.dict(mount.__grains__, {'os': 'AIX', 'kernel': 'AIX'}):
with patch.object(os.path, 'isfile', mock):
self.assertRaises(CommandExecutionError,
mount.set_filesystems, 'A', 'B', 'C')
mock_read = MagicMock(side_effect=OSError)
with patch.object(os.path, 'isfile', mock):
with patch.object(salt.utils.files, 'fopen', mock_read):
self.assertRaises(CommandExecutionError,
mount.set_filesystems, 'A', 'B', 'C')
def test_mount(self):
'''
Mount a device
@ -217,6 +311,21 @@ class MountTestCase(TestCase, LoaderModuleMockMixin):
with patch.dict(mount.__salt__, {'cmd.run_all': mock}):
self.assertTrue(mount.mount('name', 'device'))
with patch.dict(mount.__grains__, {'os': 'AIX'}):
mock = MagicMock(return_value=True)
with patch.object(os.path, 'exists', mock):
mock = MagicMock(return_value=None)
with patch.dict(mount.__salt__, {'file.mkdir': None}):
mock = MagicMock(return_value={'retcode': True,
'stderr': True})
with patch.dict(mount.__salt__, {'cmd.run_all': mock}):
self.assertTrue(mount.mount('name', 'device'))
mock = MagicMock(return_value={'retcode': False,
'stderr': False})
with patch.dict(mount.__salt__, {'cmd.run_all': mock}):
self.assertTrue(mount.mount('name', 'device'))
def test_remount(self):
'''
Attempt to remount a device, if the device is not already mounted, mount
@ -229,6 +338,13 @@ class MountTestCase(TestCase, LoaderModuleMockMixin):
with patch.object(mount, 'mount', mock):
self.assertTrue(mount.remount('name', 'device'))
with patch.dict(mount.__grains__, {'os': 'AIX'}):
mock = MagicMock(return_value=[])
with patch.object(mount, 'active', mock):
mock = MagicMock(return_value=True)
with patch.object(mount, 'mount', mock):
self.assertTrue(mount.remount('name', 'device'))
def test_umount(self):
'''
Attempt to unmount a device by specifying the directory it is
@ -282,7 +398,6 @@ class MountTestCase(TestCase, LoaderModuleMockMixin):
'''
Return a dict containing information on active swap
'''
file_data = textwrap.dedent('''\
Filename Type Size Used Priority
/dev/sda1 partition 31249404 4100 -1
@ -314,6 +429,22 @@ class MountTestCase(TestCase, LoaderModuleMockMixin):
'used': '4100'}
}, swaps
file_data = textwrap.dedent('''\
device maj,min total free
/dev/hd6 10, 2 11776MB 11765MB
''')
mock = MagicMock(return_value=file_data)
with patch.dict(mount.__grains__, {'os': 'AIX', 'kernel': 'AIX'}), \
patch.dict(mount.__salt__, {'cmd.run_stdout': mock}):
swaps = mount.swaps()
assert swaps == {
'/dev/hd6': {
'priority': '-',
'size': 12058624,
'type': 'device',
'used': 11264}
}, swaps
def test_swapon(self):
'''
Activate a swap disk
@ -338,6 +469,27 @@ class MountTestCase(TestCase, LoaderModuleMockMixin):
with patch.dict(mount.__salt__, {'cmd.run': mock}):
self.assertEqual(mount.swapon('name'), {'stats': 'name',
'new': True})
## effects of AIX
mock = MagicMock(return_value={'name': 'name'})
with patch.dict(mount.__grains__, {'kernel': 'AIX'}):
with patch.object(mount, 'swaps', mock):
self.assertEqual(mount.swapon('name'),
{'stats': 'name', 'new': False})
mock = MagicMock(return_value={})
with patch.dict(mount.__grains__, {'kernel': 'AIX'}):
with patch.object(mount, 'swaps', mock):
mock = MagicMock(return_value=None)
with patch.dict(mount.__salt__, {'cmd.run': mock}):
self.assertEqual(mount.swapon('name', False), {})
mock = MagicMock(side_effect=[{}, {'name': 'name'}])
with patch.dict(mount.__grains__, {'kernel': 'AIX'}):
with patch.object(mount, 'swaps', mock):
mock = MagicMock(return_value=None)
with patch.dict(mount.__salt__, {'cmd.run': mock}):
self.assertEqual(mount.swapon('name'), {'stats': 'name',
'new': True})
def test_swapoff(self):
'''
@ -364,14 +516,38 @@ class MountTestCase(TestCase, LoaderModuleMockMixin):
with patch.dict(mount.__salt__, {'cmd.run': mock}):
self.assertTrue(mount.swapoff('name'))
# check on AIX
mock = MagicMock(return_value={})
with patch.dict(mount.__grains__, {'kernel': 'AIX'}):
with patch.object(mount, 'swaps', mock):
self.assertEqual(mount.swapoff('name'), None)
mock = MagicMock(return_value={'name': 'name'})
with patch.dict(mount.__grains__, {'kernel': 'AIX'}):
with patch.object(mount, 'swaps', mock):
with patch.dict(mount.__grains__, {'os': 'test'}):
mock = MagicMock(return_value=None)
with patch.dict(mount.__salt__, {'cmd.run': mock}):
self.assertFalse(mount.swapoff('name'))
mock = MagicMock(side_effect=[{'name': 'name'}, {}])
with patch.dict(mount.__grains__, {'kernel': 'AIX'}):
with patch.object(mount, 'swaps', mock):
with patch.dict(mount.__grains__, {'os': 'test'}):
mock = MagicMock(return_value=None)
with patch.dict(mount.__salt__, {'cmd.run': mock}):
self.assertTrue(mount.swapoff('name'))
def test_is_mounted(self):
'''
Provide information if the path is mounted
'''
mock = MagicMock(return_value={})
with patch.object(mount, 'active', mock):
with patch.object(mount, 'active', mock), \
patch.dict(mount.__grains__, {'kernel': ''}):
self.assertFalse(mount.is_mounted('name'))
mock = MagicMock(return_value={'name': 'name'})
with patch.object(mount, 'active', mock):
with patch.object(mount, 'active', mock), \
patch.dict(mount.__grains__, {'kernel': ''}):
self.assertTrue(mount.is_mounted('name'))

View file

@ -0,0 +1,153 @@
# -*- coding: utf-8 -*-
'''
Tests for the win_pkg module
'''
# Import Python Libs
from __future__ import absolute_import, unicode_literals, print_function
# Import Salt Testing Libs
from tests.support.mixins import LoaderModuleMockMixin
from tests.support.mock import MagicMock, patch
from tests.support.unit import TestCase
# Import Salt Libs
import salt.modules.pkg_resource as pkg_resource
import salt.modules.win_pkg as win_pkg
class WinPkgInstallTestCase(TestCase, LoaderModuleMockMixin):
'''
Test cases for salt.modules.win_pkg
'''
def setup_loader_modules(self):
pkg_info = {
'3.03': {
'full_name': 'Nullsoft Install System',
'installer': 'http://download.sourceforge.net/project/nsis/NSIS%203/3.03/nsis-3.03-setup.exe',
'install_flags': '/S',
'uninstaller': '%PROGRAMFILES(x86)%\\NSIS\\uninst-nsis.exe',
'uninstall_flags': '/S',
'msiexec': False,
'reboot': False
},
'3.02': {
'full_name': 'Nullsoft Install System',
'installer': 'http://download.sourceforge.net/project/nsis/NSIS%203/3.02/nsis-3.02-setup.exe',
'install_flags': '/S',
'uninstaller': '%PROGRAMFILES(x86)%\\NSIS\\uninst-nsis.exe',
'uninstall_flags': '/S',
'msiexec': False,
'reboot': False
}
}
return{
win_pkg: {
'_get_latest_package_version': MagicMock(return_value='3.03'),
'_get_package_info': MagicMock(return_value=pkg_info),
'__salt__': {
'pkg_resource.add_pkg': pkg_resource.add_pkg,
'pkg_resource.parse_targets': pkg_resource.parse_targets,
'pkg_resource.sort_pkglist': pkg_resource.sort_pkglist,
'pkg_resource.stringify': pkg_resource.stringify,
},
},
pkg_resource: {
'__grains__': {
'os': 'Windows'
}
},
}
def test_pkg_install_not_found(self):
'''
Test pkg.install when the Version is NOT FOUND in the Software
Definition
'''
ret_reg = {'Nullsoft Install System': '3.03'}
# The 2nd time it's run with stringify
se_list_pkgs = {'nsis': ['3.03']}
with patch.object(win_pkg, 'list_pkgs', return_value=se_list_pkgs), \
patch.object(win_pkg, '_get_reg_software', return_value=ret_reg):
expected = {'nsis': {'not found': '3.01'}}
result = win_pkg.install(name='nsis', version='3.01')
self.assertDictEqual(expected, result)
def test_pkg_install_rollback(self):
'''
test pkg.install rolling back to a previous version
'''
ret_reg = {'Nullsoft Install System': '3.03'}
# The 2nd time it's run, pkg.list_pkgs uses with stringify
se_list_pkgs = [{'nsis': ['3.03']},
{'nsis': '3.02'}]
with patch.object(win_pkg, 'list_pkgs', side_effect=se_list_pkgs), \
patch.object(
win_pkg, '_get_reg_software', return_value=ret_reg), \
patch.dict(
win_pkg.__salt__,
{'cp.is_cached': MagicMock(return_value=False)}), \
patch.dict(
win_pkg.__salt__,
{'cp.cache_file':
MagicMock(return_value='C:\\fake\\path.exe')}), \
patch.dict(
win_pkg.__salt__,
{'cmd.run_all':
MagicMock(return_value={'retcode': 0})}):
expected = {'nsis': {'new': '3.02', 'old': '3.03'}}
result = win_pkg.install(name='nsis', version='3.02')
self.assertDictEqual(expected, result)
def test_pkg_install_existing(self):
'''
test pkg.install when the package is already installed
no version passed
'''
ret_reg = {'Nullsoft Install System': '3.03'}
# The 2nd time it's run, pkg.list_pkgs uses with stringify
se_list_pkgs = {'nsis': ['3.03']}
with patch.object(win_pkg, 'list_pkgs', return_value=se_list_pkgs), \
patch.object(
win_pkg, '_get_reg_software', return_value=ret_reg), \
patch.dict(
win_pkg.__salt__,
{'cp.is_cached': MagicMock(return_value=False)}), \
patch.dict(
win_pkg.__salt__,
{'cp.cache_file':
MagicMock(return_value='C:\\fake\\path.exe')}), \
patch.dict(
win_pkg.__salt__,
{'cmd.run_all':
MagicMock(return_value={'retcode': 0})}):
expected = {}
result = win_pkg.install(name='nsis')
self.assertDictEqual(expected, result)
def test_pkg_install_existing_with_version(self):
'''
test pkg.install when the package is already installed
A version is passed
'''
ret_reg = {'Nullsoft Install System': '3.03'}
# The 2nd time it's run, pkg.list_pkgs uses with stringify
se_list_pkgs = {'nsis': ['3.03']}
with patch.object(win_pkg, 'list_pkgs', return_value=se_list_pkgs), \
patch.object(
win_pkg, '_get_reg_software', return_value=ret_reg), \
patch.dict(
win_pkg.__salt__,
{'cp.is_cached': MagicMock(return_value=False)}), \
patch.dict(
win_pkg.__salt__,
{'cp.cache_file':
MagicMock(return_value='C:\\fake\\path.exe')}), \
patch.dict(
win_pkg.__salt__,
{'cmd.run_all':
MagicMock(return_value={'retcode': 0})}):
expected = {}
result = win_pkg.install(name='nsis', version='3.03')
self.assertDictEqual(expected, result)

View file

@ -44,6 +44,13 @@ class MountTestCase(TestCase, LoaderModuleMockMixin):
superopts2 = ['uid=510', 'gid=100', 'username=cifsuser',
'domain=cifsdomain']
name3 = os.path.realpath('/mnt/jfs2')
device3 = '/dev/hd1'
fstype3 = 'jfs2'
opts3 = ['']
superopts3 = ['uid=510', 'gid=100', 'username=jfs2user',
'domain=jfs2sdomain']
ret = {'name': name,
'result': False,
'comment': '',
@ -57,7 +64,11 @@ class MountTestCase(TestCase, LoaderModuleMockMixin):
mock_mnt = MagicMock(return_value={name: {'device': device, 'opts': [],
'superopts': []},
name2: {'device': device2, 'opts': opts2,
'superopts': superopts2}})
'superopts': superopts2},
name3: {'device': device3, 'opts': opts3,
'superopts': superopts3}})
mock_aixfs_retn = MagicMock(return_value='present')
mock_emt = MagicMock(return_value={})
mock_str = MagicMock(return_value='salt')
mock_user = MagicMock(return_value={'uid': 510})
@ -91,7 +102,8 @@ class MountTestCase(TestCase, LoaderModuleMockMixin):
ret)
umount1 = ("Forced unmount because devices don't match. "
"Wanted: {0}, current: {1}, {1}".format(os.path.realpath('/dev/sdb6'), device))
"Wanted: {0}, current: {1}, {1}"
.format(os.path.realpath('/dev/sdb6'), device))
comt = ('Unable to unmount')
ret.update({'comment': comt, 'result': None,
'changes': {'umount': umount1}})
@ -124,7 +136,8 @@ class MountTestCase(TestCase, LoaderModuleMockMixin):
with patch.dict(mount.__opts__, {'test': True}), \
patch('os.path.exists', MagicMock(return_value=False)):
comt = ('{0} does not exist and would neither be created nor mounted. '
'{0} needs to be written to the fstab in order to be made persistent.'.format(name))
'{0} needs to be written to the fstab in order to be made persistent.'
.format(name))
ret.update({'comment': comt, 'result': None})
self.assertDictEqual(mount.mounted(name, device, fstype,
mount=False), ret)
@ -185,6 +198,26 @@ class MountTestCase(TestCase, LoaderModuleMockMixin):
'gid=group1']),
ret)
with patch.dict(mount.__grains__, {'os': 'AIX'}):
with patch.dict(mount.__salt__, {'mount.active': mock_mnt,
'mount.mount': mock_str,
'mount.umount': mock_f,
'mount.read_mount_cache': mock_read_cache,
'mount.write_mount_cache': mock_write_cache,
'mount.set_filesystems': mock_aixfs_retn,
'user.info': mock_user,
'group.info': mock_group}):
with patch.dict(mount.__opts__, {'test': True}):
with patch.object(os.path, 'exists', mock_t):
comt = 'Target was already mounted. Entry already exists in the fstab.'
ret.update({'name': name3, 'result': True})
ret.update({'comment': comt, 'changes': {}})
self.assertDictEqual(mount.mounted(name3, device3,
fstype3,
opts=['uid=user1',
'gid=group1']),
ret)
# 'swap' function tests: 1
def test_swap(self):
@ -203,44 +236,69 @@ class MountTestCase(TestCase, LoaderModuleMockMixin):
mock_swp = MagicMock(return_value=[name])
mock_fs = MagicMock(return_value={'none': {'device': name,
'fstype': 'xfs'}})
mock_aixfs = MagicMock(return_value={name: {'dev': name,
'fstype': 'jfs2'}})
mock_emt = MagicMock(return_value={})
with patch.dict(mount.__salt__, {'mount.swaps': mock_swp,
'mount.fstab': mock_fs,
'file.is_link': mock_f}):
with patch.dict(mount.__opts__, {'test': True}):
comt = ('Swap {0} is set to be added to the '
'fstab and to be activated'.format(name))
ret.update({'comment': comt})
self.assertDictEqual(mount.swap(name), ret)
with patch.dict(mount.__grains__, {'os': 'test'}):
with patch.dict(mount.__salt__, {'mount.swaps': mock_swp,
'mount.fstab': mock_fs,
'file.is_link': mock_f}):
with patch.dict(mount.__opts__, {'test': True}):
comt = ('Swap {0} is set to be added to the '
'fstab and to be activated'.format(name))
ret.update({'comment': comt})
self.assertDictEqual(mount.swap(name), ret)
with patch.dict(mount.__opts__, {'test': False}):
comt = ('Swap {0} already active'.format(name))
ret.update({'comment': comt, 'result': True})
self.assertDictEqual(mount.swap(name), ret)
with patch.dict(mount.__salt__, {'mount.fstab': mock_emt,
'mount.set_fstab': mock}):
with patch.dict(mount.__opts__, {'test': False}):
comt = ('Swap {0} already active'.format(name))
ret.update({'comment': comt, 'result': True})
self.assertDictEqual(mount.swap(name), ret)
comt = ('Swap /mnt/sdb already active. '
'Added new entry to the fstab.')
ret.update({'comment': comt, 'result': True,
'changes': {'persist': 'new'}})
with patch.dict(mount.__salt__, {'mount.fstab': mock_emt,
'mount.set_fstab': mock}):
comt = ('Swap {0} already active'.format(name))
ret.update({'comment': comt, 'result': True})
self.assertDictEqual(mount.swap(name), ret)
comt = ('Swap /mnt/sdb already active. '
'Added new entry to the fstab.')
ret.update({'comment': comt, 'result': True,
'changes': {'persist': 'new'}})
self.assertDictEqual(mount.swap(name), ret)
comt = ('Swap /mnt/sdb already active. '
'Updated the entry in the fstab.')
ret.update({'comment': comt, 'result': True,
'changes': {'persist': 'update'}})
self.assertDictEqual(mount.swap(name), ret)
comt = ('Swap /mnt/sdb already active. '
'However, the fstab was not found.')
ret.update({'comment': comt, 'result': False,
'changes': {}})
self.assertDictEqual(mount.swap(name), ret)
with patch.dict(mount.__grains__, {'os': 'AIX'}):
with patch.dict(mount.__salt__, {'mount.swaps': mock_swp,
'mount.filesystems': mock_aixfs,
'file.is_link': mock_f}):
with patch.dict(mount.__opts__, {'test': True}):
comt = ('Swap {0} already active'.format(name))
ret.update({'comment': comt, 'result': True})
self.assertDictEqual(mount.swap(name), ret)
comt = ('Swap /mnt/sdb already active. '
'Updated the entry in the fstab.')
ret.update({'comment': comt, 'result': True,
'changes': {'persist': 'update'}})
with patch.dict(mount.__opts__, {'test': False}):
comt = ('Swap {0} already active. swap not present'
' in /etc/filesystems on AIX.'.format(name))
ret.update({'comment': comt, 'result': False})
self.assertDictEqual(mount.swap(name), ret)
comt = ('Swap /mnt/sdb already active. '
'However, the fstab was not found.')
ret.update({'comment': comt, 'result': False,
'changes': {}})
self.assertDictEqual(mount.swap(name), ret)
with patch.dict(mount.__salt__, {'mount.filesystems': mock_emt,
'mount.set_filesystems': mock}):
comt = ('Swap {0} already active. swap not present'
' in /etc/filesystems on AIX.'.format(name))
ret.update({'comment': comt, 'result': False})
self.assertDictEqual(mount.swap(name), ret)
# 'unmounted' function tests: 1
@ -257,11 +315,22 @@ class MountTestCase(TestCase, LoaderModuleMockMixin):
'changes': {}}
mock_f = MagicMock(return_value=False)
mock_t = MagicMock(return_value=True)
mock_dev = MagicMock(return_value={name: {'device': device}})
mock_fs = MagicMock(return_value={name: {'device': name}})
mock_mnt = MagicMock(side_effect=[{name: {}}, {}, {}, {}])
name3 = os.path.realpath('/mnt/jfs2')
device3 = '/dev/hd1'
fstype3 = 'jfs2'
opts3 = ['']
mock_mnta = MagicMock(return_value={name3: {'device': device3, 'opts': opts3}})
mock_aixfs = MagicMock(return_value={name: {'dev': name3, 'fstype': fstype3}})
mock_delete_cache = MagicMock(return_value=True)
comt3 = ('Mount point /mnt/sdb is unmounted but needs to be purged '
'from /etc/auto_salt to be made persistent')
with patch.dict(mount.__grains__, {'os': 'Darwin'}):
with patch.dict(mount.__salt__, {'mount.active': mock_mnt,
'mount.automaster': mock_fs,
@ -273,7 +342,7 @@ class MountTestCase(TestCase, LoaderModuleMockMixin):
self.assertDictEqual(mount.unmounted(name, device), ret)
comt = ('Target was already unmounted. '
'fstab entry for device /dev/sdb5 not found')
'fstab entry for device {0} not found'.format(device))
ret.update({'comment': comt, 'result': True})
self.assertDictEqual(mount.unmounted(name, device,
persist=True), ret)
@ -288,6 +357,37 @@ class MountTestCase(TestCase, LoaderModuleMockMixin):
ret.update({'comment': comt, 'result': True})
self.assertDictEqual(mount.unmounted(name, device), ret)
with patch.dict(mount.__grains__, {'os': 'AIX'}):
with patch.dict(mount.__salt__, {'mount.active': mock_mnta,
'mount.filesystems': mock_aixfs,
'file.is_link': mock_f}):
with patch.dict(mount.__opts__, {'test': True}):
comt = ('Target was already unmounted')
ret.update({'comment': comt, 'result': True})
self.assertDictEqual(mount.unmounted(name, device), ret)
comt = ('Target was already unmounted. '
'fstab entry for device /dev/sdb5 not found')
ret.update({'comment': comt, 'result': True})
self.assertDictEqual(mount.unmounted(name, device,
persist=True), ret)
with patch.dict(mount.__salt__,
{'mount.filesystems': mock_dev}):
comt = ('Mount point {0} is mounted but should not '
'be'.format(name3))
ret.update({'comment': comt, 'result': None, 'name': name3})
self.assertDictEqual(mount.unmounted(name3, device3,
persist=True), ret)
with patch.dict(mount.__opts__, {'test': False}), \
patch.dict(mount.__salt__, {'mount.umount': mock_t,
'mount.delete_mount_cache': mock_delete_cache}):
comt = ('Target was successfully unmounted')
ret.update({'comment': comt, 'result': True,
'name': name3, 'changes': {'umount': True}})
self.assertDictEqual(mount.unmounted(name3, device3), ret)
# 'mod_watch' function tests: 1
def test_mod_watch(self):

View file

@ -96,6 +96,19 @@ vpn0: flags=120002200850<POINTOPOINT,RUNNING,MULTICAST,NONUD,IPv6,PHYSRUNNING> m
inet6 ::/0 --> fe80::b2d6:7c10
'''
NETBSD = '''\
vioif0: flags=0x8943<UP,BROADCAST,RUNNING,PROMISC,SIMPLEX,MULTICAST> mtu 1500
ec_capabilities=1<VLAN_MTU>
ec_enabled=0
address: 00:a0:98:e6:83:18
inet 192.168.1.80/24 broadcast 192.168.1.255 flags 0x0
inet6 fe80::2a0:98ff:fee6:8318%vioif0/64 flags 0x0 scopeid 0x1
lo0: flags=0x8049<UP,LOOPBACK,RUNNING,MULTICAST> mtu 33624
inet 127.0.0.1/8 flags 0x0
inet6 ::1/128 flags 0x20<NODAD>
inet6 fe80::1%lo0/64 flags 0x0 scopeid 0x2
'''
FREEBSD_SOCKSTAT = '''\
USER COMMAND PID FD PROTO LOCAL ADDRESS FOREIGN ADDRESS
root python2.7 1294 41 tcp4 127.0.0.1:61115 127.0.0.1:4506
@ -319,6 +332,24 @@ class NetworkTestCase(TestCase):
'up': True}}
self.assertEqual(interfaces, expected_interfaces)
def test_interfaces_ifconfig_netbsd(self):
interfaces = network._netbsd_interfaces_ifconfig(NETBSD)
self.assertEqual(interfaces,
{'lo0': {'inet': [{'address': '127.0.0.1', 'netmask': '255.0.0.0'}],
'inet6': [{'address': 'fe80::1',
'prefixlen': '64',
'scope': 'lo0'}],
'up': True},
'vioif0': {'hwaddr': '00:a0:98:e6:83:18',
'inet': [{'address': '192.168.1.80',
'broadcast': '192.168.1.255',
'netmask': '255.255.255.0'}],
'inet6': [{'address': 'fe80::2a0:98ff:fee6:8318',
'prefixlen': '64',
'scope': 'vioif0'}],
'up': True}}
)
def test_freebsd_remotes_on(self):
with patch('salt.utils.platform.is_sunos', lambda: False):
with patch('salt.utils.platform.is_freebsd', lambda: True):

View file

@ -8,6 +8,7 @@ integration.modules.test_autoruns
integration.modules.test_beacons
integration.modules.test_config
integration.modules.test_cp
integration.modules.test_cmdmod
integration.modules.test_data
integration.modules.test_disk
integration.modules.test_firewall