mirror of
https://github.com/saltstack/salt.git
synced 2025-04-17 10:10:20 +00:00
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:
commit
5ff1ddb0dd
48 changed files with 1492 additions and 286 deletions
7
Gemfile
7
Gemfile
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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))
|
||||
|
|
23
salt/key.py
23
salt/key.py
|
@ -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):
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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 ''
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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])
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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'}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
'''
|
||||
|
|
|
@ -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):
|
||||
'''
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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}):
|
||||
|
|
|
@ -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'))
|
||||
|
|
153
tests/unit/modules/test_win_pkg.py
Normal file
153
tests/unit/modules/test_win_pkg.py
Normal 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)
|
|
@ -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):
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue