Merge branch '2018.3' into 'develop'

Conflicts:
  - salt/client/ssh/__init__.py
  - salt/utils/thin.py
This commit is contained in:
rallytime 2018-06-30 09:48:06 -04:00
commit bf6c788350
No known key found for this signature in database
GPG key ID: E8F1A4B90D0DEA19
34 changed files with 6132 additions and 121 deletions

View file

@ -1,6 +1,6 @@
---
<% vagrant = system('gem list -i kitchen-vagrant 2>/dev/null >/dev/null') %>
<% version = '2017.7.4' %>
<% version = '2017.7.6' %>
<% platformsfile = ENV['SALT_KITCHEN_PLATFORMS'] || '.kitchen/platforms.yml' %>
<% driverfile = ENV['SALT_KITCHEN_DRIVER'] || '.kitchen/driver.yml' %>
<% verifierfile = ENV['SALT_KITCHEN_VERIFIER'] || '.kitchen/verifier.yml' %>

View file

@ -148,22 +148,23 @@ Why aren't my custom modules/states/etc. available on my Minions?
-----------------------------------------------------------------
Custom modules are synced to Minions when
:mod:`saltutil.sync_modules <salt.modules.saltutil.sync_modules>`,
or :mod:`saltutil.sync_all <salt.modules.saltutil.sync_all>` is run.
Custom modules are also synced by :mod:`state.apply` when run without
any arguments.
:py:func:`saltutil.sync_modules <salt.modules.saltutil.sync_modules>`,
or :py:func:`saltutil.sync_all <salt.modules.saltutil.sync_all>` is run.
Similarly, custom states are synced to Minions when :py:func:`saltutil.sync_states
<salt.modules.saltutil.sync_states>`, or :py:func:`saltutil.sync_all
<salt.modules.saltutil.sync_all>` is run.
Similarly, custom states are synced to Minions
when :mod:`state.apply <salt.modules.state.apply_>`,
:mod:`saltutil.sync_states <salt.modules.saltutil.sync_states>`, or
:mod:`saltutil.sync_all <salt.modules.saltutil.sync_all>` is run.
They are both also synced when a :ref:`highstate <running-highstate>` is
triggered.
Custom states are also synced by :mod:`state.apply<salt.modules.state.apply_>`
when run without any arguments.
As of the Fluorine release, as well as 2017.7.7 and 2018.3.2 in their
respective release cycles, the ``sync`` argument to :py:func:`state.apply
<salt.modules.state.apply_>`/:py:func:`state.sls <salt.modules.state.sls>` can
be used to sync custom types when running individual SLS files.
Other custom types (renderers, outputters, etc.) have similar behavior, see the
documentation for the :mod:`saltutil <salt.modules.saltutil>` module for more
documentation for the :py:func:`saltutil <salt.modules.saltutil>` module for more
information.
:ref:`This reactor example <minion-start-reactor>` can be used to automatically

View file

@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SALT-API" "1" "May 09, 2018" "2018.3.1" "Salt"
.TH "SALT-API" "1" "Jun 14, 2018" "2018.3.2" "Salt"
.SH NAME
salt-api \- salt-api Command
.

View file

@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SALT-CALL" "1" "May 09, 2018" "2018.3.1" "Salt"
.TH "SALT-CALL" "1" "Jun 14, 2018" "2018.3.2" "Salt"
.SH NAME
salt-call \- salt-call Documentation
.

View file

@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SALT-CLOUD" "1" "May 09, 2018" "2018.3.1" "Salt"
.TH "SALT-CLOUD" "1" "Jun 14, 2018" "2018.3.2" "Salt"
.SH NAME
salt-cloud \- Salt Cloud Command
.

View file

@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SALT-CP" "1" "May 09, 2018" "2018.3.1" "Salt"
.TH "SALT-CP" "1" "Jun 14, 2018" "2018.3.2" "Salt"
.SH NAME
salt-cp \- salt-cp Documentation
.

View file

@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SALT-KEY" "1" "May 09, 2018" "2018.3.1" "Salt"
.TH "SALT-KEY" "1" "Jun 14, 2018" "2018.3.2" "Salt"
.SH NAME
salt-key \- salt-key Documentation
.

View file

@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SALT-MASTER" "1" "May 09, 2018" "2018.3.1" "Salt"
.TH "SALT-MASTER" "1" "Jun 14, 2018" "2018.3.2" "Salt"
.SH NAME
salt-master \- salt-master Documentation
.

View file

@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SALT-MINION" "1" "May 09, 2018" "2018.3.1" "Salt"
.TH "SALT-MINION" "1" "Jun 14, 2018" "2018.3.2" "Salt"
.SH NAME
salt-minion \- salt-minion Documentation
.

View file

@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SALT-PROXY" "1" "May 09, 2018" "2018.3.1" "Salt"
.TH "SALT-PROXY" "1" "Jun 14, 2018" "2018.3.2" "Salt"
.SH NAME
salt-proxy \- salt-proxy Documentation
.

View file

@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SALT-RUN" "1" "May 09, 2018" "2018.3.1" "Salt"
.TH "SALT-RUN" "1" "Jun 14, 2018" "2018.3.2" "Salt"
.SH NAME
salt-run \- salt-run Documentation
.

View file

@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SALT-SSH" "1" "May 09, 2018" "2018.3.1" "Salt"
.TH "SALT-SSH" "1" "Jun 14, 2018" "2018.3.2" "Salt"
.SH NAME
salt-ssh \- salt-ssh Documentation
.

View file

@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SALT-SYNDIC" "1" "May 09, 2018" "2018.3.1" "Salt"
.TH "SALT-SYNDIC" "1" "Jun 14, 2018" "2018.3.2" "Salt"
.SH NAME
salt-syndic \- salt-syndic Documentation
.

View file

@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SALT-UNITY" "1" "May 09, 2018" "2018.3.1" "Salt"
.TH "SALT-UNITY" "1" "Jun 14, 2018" "2018.3.2" "Salt"
.SH NAME
salt-unity \- salt-unity Command
.

View file

@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SALT" "1" "May 09, 2018" "2018.3.1" "Salt"
.TH "SALT" "1" "Jun 14, 2018" "2018.3.2" "Salt"
.SH NAME
salt \- salt
.

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
.\" Man page generated from reStructuredText.
.
.TH "SPM" "1" "May 09, 2018" "2018.3.1" "Salt"
.TH "SPM" "1" "Jun 14, 2018" "2018.3.2" "Salt"
.SH NAME
spm \- Salt Package Manager Command
.

View file

@ -479,7 +479,7 @@ several useful attributes:
def test_something(self):
with patch('salt.utils.files.fopen', mock_open(read_data=b'foo\n')) as mopen:
with patch('salt.utils.files.fopen', mock_open(read_data=b'foo\n')) as m_open:
mymod.myfunc()
# Assert that only two opens attempted
assert m_open.call_count == 2
@ -487,7 +487,7 @@ several useful attributes:
assert all(call.args[0] == '/etc/foo.conf' for call in m_open.calls)
# Asser that the first open was for binary read, and the
# second was for binary write.
assert m_open.calls = [
assert m_open.calls == [
MockCall('/etc/foo.conf', 'rb'),
MockCall('/etc/foo.conf', 'wb'),
]

View file

@ -5,23 +5,64 @@ In Progress: Salt 2017.7.7 Release Notes
Version 2017.7.7 is an **unreleased** bugfix release for :ref:`2017.7.0 <release-2017-7-0>`.
This release is still in progress and has not been released yet.
The ``2017.7.7`` release contains only a single fix for Issue `#48038`_, which
is a critical bug that occurs in a multi-syndic setup where the same job is run
multiple times on a minion.
The ``2017.7.7`` release contains only a small number of fixes, which are detailed
below.
This release fixes two critical issues.
The first is Issue `#48038`_, which is a critical bug that occurs in a multi-syndic
setup where the same job is run multiple times on a minion.
The second issue is `#48130`_. This bug appears in certain setups where the Master
reports a Minion time-out, even though the job is still running on the Minion.
Both of these issues have been fixed with this release.
Statistics
==========
- Total Merges: **1**
- Total Issue References: **1**
- Total PR References: **2**
- Total Merges: **5**
- Total Issue References: **2**
- Total PR References: **6**
- Contributors: **2** (`garethgreenaway`_, `rallytime`_)
- Contributors: **3** (`garethgreenaway`_, `gtmanfred`_, `rallytime`_)
Changelog for v2017.7.6..v2017.7.7
==================================
*Generated at: 2018-06-14 15:43:34 UTC*
*Generated at: 2018-06-17 19:26:52 UTC*
* **ISSUE** `#48130`_: (`rmarchei`_) Minion timeouts with 2018.3.1 (refs: `#48157`_)
* **PR** `#48157`_: (`gtmanfred`_) always listen when gathering job info
@ *2018-06-17 19:04:09 UTC*
* 8af4452134 Merge pull request `#48157`_ from gtmanfred/2017.7.7
* d8209e8a40 always listen when gathering job info
* **PR** `#48140`_: (`rallytime`_) Update man pages for 2017.7.7
@ *2018-06-14 21:22:43 UTC*
* b98c52ee51 Merge pull request `#48140`_ from rallytime/man-pages-2017.7.7
* 8893bf0d4c Update man pages for 2017.7.7
* **PR** `#48136`_: (`gtmanfred`_) [2017.7.7] bootstrap kitchen branch tests with 2017.7.6
@ *2018-06-14 21:20:16 UTC*
* baa0363336 Merge pull request `#48136`_ from gtmanfred/2017.7.7
* fce1c31146 bootstrap kitchen branch tests with 2017.7.6
* **PR** `#48134`_: (`rallytime`_) Add release notes file for 2017.7.7
@ *2018-06-14 16:31:34 UTC*
* b0ba08f4d9 Merge pull request `#48134`_ from rallytime/release-notes-2017.7.7
* 217005b8f1 Add missing `v` for tag reference
* d53569d1e3 Add release notes file for 2017.7.7
* **ISSUE** `#48038`_: (`austinpapp`_) jobs are not dedup'ing minion side (refs: `#48075`_)
@ -37,6 +78,13 @@ Changelog for v2017.7.6..v2017.7.7
.. _`#48038`: https://github.com/saltstack/salt/issues/48038
.. _`#48075`: https://github.com/saltstack/salt/pull/48075
.. _`#48098`: https://github.com/saltstack/salt/pull/48098
.. _`#48130`: https://github.com/saltstack/salt/issues/48130
.. _`#48134`: https://github.com/saltstack/salt/pull/48134
.. _`#48136`: https://github.com/saltstack/salt/pull/48136
.. _`#48140`: https://github.com/saltstack/salt/pull/48140
.. _`#48157`: https://github.com/saltstack/salt/pull/48157
.. _`austinpapp`: https://github.com/austinpapp
.. _`garethgreenaway`: https://github.com/garethgreenaway
.. _`gtmanfred`: https://github.com/gtmanfred
.. _`rallytime`: https://github.com/rallytime
.. _`rmarchei`: https://github.com/rmarchei

View file

@ -330,7 +330,13 @@ Nested pillar values can also be set via the command line:
.. code-block:: bash
salt '*' state.sls my_sls_file pillar='{"foo": {"bar": "baz"}}'
salt '*' state.sls my_sls_file pillar='{"foo": {"bar": "baz"}}'
Lists can be passed via command line pillar data as follows:
.. code-block:: bash
salt '*' state.sls my_sls_file pillar='{"some_list": ["foo", "bar", "baz"]}'
.. note::

View file

@ -139,13 +139,18 @@ where it is necessary to invoke the same function from a custom :ref:`outputter
<all-salt.output>`/returner, as well as an execution module.
Utility modules placed in ``salt://_utils/`` will be synced to the minions when
any of the following Salt functions are called:
a :ref:`highstate <running-highstate>` is run, as well as when any of the
following Salt functions are called:
* :mod:`state.apply <salt.modules.state.apply_>`
* :mod:`saltutil.sync_utils <salt.modules.saltutil.sync_utils>`
* :mod:`saltutil.sync_all <salt.modules.saltutil.sync_all>`
* :py:func:`saltutil.sync_utils <salt.modules.saltutil.sync_utils>`
* :py:func:`saltutil.sync_all <salt.modules.saltutil.sync_all>`
As of the Fluorine release, as well as 2017.7.7 and 2018.3.2 in their
respective release cycles, the ``sync`` argument to :py:func:`state.apply
<salt.modules.state.apply_>`/:py:func:`state.sls <salt.modules.state.sls>` can
be used to sync custom types when running individual SLS files.
To sync to the Master, use either of the following:
* :mod:`saltutil.sync_utils <salt.runners.saltutil.sync_utils>`
* :mod:`saltutil.sync_all <salt.runners.saltutil.sync_all>`
* :py:func:`saltutil.sync_utils <salt.runners.saltutil.sync_utils>`
* :py:func:`saltutil.sync_all <salt.runners.saltutil.sync_all>`

View file

@ -228,7 +228,7 @@ class LocalClient(object):
# Looks like the timeout is invalid, use config
return self.opts['timeout']
def gather_job_info(self, jid, tgt, tgt_type, **kwargs):
def gather_job_info(self, jid, tgt, tgt_type, listen=True, **kwargs):
'''
Return the information about a given job
'''
@ -240,6 +240,7 @@ class LocalClient(object):
arg=[jid],
tgt_type=tgt_type,
timeout=timeout,
listen=listen,
**kwargs
)

View file

@ -1249,7 +1249,10 @@ ARGS = {arguments}\n'''.format(config=self.minion_config,
shim_tmp_file.write(salt.utils.stringutils.to_bytes(cmd_str))
# Copy shim to target system, under $HOME/.<randomized name>
target_shim_file = '.{0}.{1}'.format(binascii.hexlify(os.urandom(6)).decode('ascii'), extension)
target_shim_file = '.{0}.{1}'.format(
binascii.hexlify(os.urandom(6)).decode('ascii'),
extension
)
if self.winrm:
target_shim_file = saltwinshell.get_target_shim_file(self, target_shim_file)
self.shell.send(shim_tmp_file.name, target_shim_file, makedirs=True)

View file

@ -366,13 +366,11 @@ class FileserverUpdate(salt.utils.process.SignalHandlingMultiprocessingProcess):
self.__init__(
state['opts'],
log_queue=state['log_queue'],
log_queue_level=state['log_queue_level']
)
def __getstate__(self):
return {'opts': self.opts,
'log_queue': self.log_queue,
'log_queue_level': self.log_queue_level
}
def fill_buckets(self):

View file

@ -2268,22 +2268,22 @@ def _change_state(cmd,
# as te command itself mess with double forks; we must not
# communicate with it, but just wait for the exit status
pkwargs = {'python_shell': False,
'redirect_stderr': True,
'with_communicate': with_communicate,
'use_vt': use_vt,
'stdin': stdin,
'stdout': stdout,
'stderr': stderr}
'stdout': stdout}
for i in [a for a in pkwargs]:
val = pkwargs[i]
if val is _marker:
pkwargs.pop(i, None)
error = __salt__['cmd.run_stderr'](cmd, **pkwargs)
_cmdout = __salt__['cmd.run_all'](cmd, **pkwargs)
if error:
if _cmdout['retcode'] != 0:
raise CommandExecutionError(
'Error changing state for container \'{0}\' using command '
'\'{1}\': {2}'.format(name, cmd, error)
'\'{1}\': {2}'.format(name, cmd, _cmdout['stdout'])
)
if expected is not None:
# some commands do not wait, so we will

View file

@ -602,8 +602,7 @@ def template_str(tem, queue=False, **kwargs):
return ret
def apply_(mods=None,
**kwargs):
def apply_(mods=None, **kwargs):
'''
.. versionadded:: 2015.5.0
@ -743,6 +742,22 @@ def apply_(mods=None,
.. code-block:: bash
salt '*' state.apply test localconfig=/path/to/minion.yml
sync_mods
If specified, the desired custom module types will be synced prior to
running the SLS files:
.. code-block:: bash
salt '*' state.apply test sync_mods=states,modules
salt '*' state.apply test sync_mods=all
.. note::
This option is ignored when no SLS files are specified, as a
:ref:`highstate <running-highstate>` automatically syncs all custom
module types.
.. versionadded:: 2017.7.8,2018.3.3,Fluorine
'''
if mods:
return sls(mods, **kwargs)
@ -1068,7 +1083,7 @@ def highstate(test=None, queue=False, **kwargs):
return ret
def sls(mods, test=None, exclude=None, queue=False, **kwargs):
def sls(mods, test=None, exclude=None, queue=False, sync_mods=None, **kwargs):
'''
Execute the states in one or more SLS files
@ -1160,6 +1175,17 @@ def sls(mods, test=None, exclude=None, queue=False, **kwargs):
.. versionadded:: 2015.8.4
sync_mods
If specified, the desired custom module types will be synced prior to
running the SLS files:
.. code-block:: bash
salt '*' state.sls test sync_mods=states,modules
salt '*' state.sls test sync_mods=all
.. versionadded:: 2017.7.8,2018.3.3,Fluorine
CLI Example:
.. code-block:: bash
@ -1223,6 +1249,28 @@ def sls(mods, test=None, exclude=None, queue=False, **kwargs):
'{0}.cache.p'.format(kwargs.get('cache_name', 'highstate'))
)
if sync_mods is True:
sync_mods = ['all']
if sync_mods is not None:
sync_mods = salt.utils.args.split_input(sync_mods)
else:
sync_mods = []
if 'all' in sync_mods and sync_mods != ['all']:
# Prevent unnecessary extra syncing
sync_mods = ['all']
for module_type in sync_mods:
try:
__salt__['saltutil.sync_{0}'.format(module_type)](
saltenv=opts['saltenv']
)
except KeyError:
log.warning(
'Invalid custom module type \'%s\', ignoring',
module_type
)
try:
st_ = salt.state.HighState(opts,
pillar_override,

View file

@ -164,7 +164,7 @@ def action(func=None,
instances,
provider,
instance,
**salt.utils.args.clean_kwargs(**kwargs)
salt.utils.args.clean_kwargs(**kwargs)
)
except SaltCloudConfigError as err:
log.error(err)

View file

@ -69,7 +69,7 @@ def set_(key, value, service=None, profile=None): # pylint: disable=W0613
'''
key, profile = _parse_key(key, profile)
cache = salt.cache.Cache(__opts__)
cache.set(profile['bank'], key=key, value=value)
cache.store(profile['bank'], key, value)
return get(key, service, profile)

View file

@ -179,6 +179,18 @@ def _fail(ret, msg, comments=None):
return ret
def _already_cloned(ret, target, branch=None, comments=None):
ret['result'] = True
ret['comment'] = 'Repository already exists at {0}{1}'.format(
target,
' and is checked out to branch \'{0}\''.format(branch) if branch else ''
)
if comments:
ret['comment'] += '\n\nChanges already made: '
ret['comment'] += _format_comments(comments)
return ret
def _failed_fetch(ret, exc, comments=None):
msg = (
'Fetch failed. Set \'force_fetch\' to True to force the fetch if the '
@ -2675,6 +2687,202 @@ def detached(name,
return ret
def cloned(name,
target=None,
branch=None,
user=None,
password=None,
identity=None,
https_user=None,
https_pass=None,
output_encoding=None):
'''
.. versionadded:: 2018.3.2,Fluorine
Ensure that a repository has been cloned to the specified target directory.
If not, clone that repository. No fetches will be performed once cloned.
name
Address of the remote repository
target
Name of the target directory where repository should be cloned
branch
Remote branch to check out. If unspecified, the default branch (i.e.
the one to the remote HEAD points) will be checked out.
.. note::
The local branch name will match the remote branch name. If the
branch name is changed, then that branch will be checked out
locally, but keep in mind that remote repository will not be
fetched. If your use case requires that you keep the clone up to
date with the remote repository, then consider using
:py:func:`git.latest <salt.states.git.latest>`.
user
User under which to run git commands. By default, commands are run by
the user under which the minion is running.
password
Windows only. Required when specifying ``user``. This parameter will be
ignored on non-Windows platforms.
identity
Path to a private key to use for ssh URLs. Works the same way as in
:py:func:`git.latest <salt.states.git.latest>`, see that state's
documentation for more information.
https_user
HTTP Basic Auth username for HTTPS (only) clones
https_pass
HTTP Basic Auth password for HTTPS (only) clones
output_encoding
Use this option to specify which encoding to use to decode the output
from any git commands which are run. This should not be needed in most
cases.
.. note::
This should only be needed if the files in the repository were
created with filenames using an encoding other than UTF-8 to handle
Unicode characters.
'''
ret = {'name': name, 'result': False, 'comment': '', 'changes': {}}
if target is None:
ret['comment'] = '\'target\' argument is required'
return ret
elif not isinstance(target, six.string_types):
target = six.text_type(target)
if not os.path.isabs(target):
ret['comment'] = '\'target\' path must be absolute'
return ret
if branch is not None:
if not isinstance(branch, six.string_types):
branch = six.text_type(branch)
if not branch:
ret['comment'] = 'Invalid \'branch\' argument'
return ret
if not os.path.exists(target):
need_clone = True
else:
try:
__salt__['git.status'](target,
user=user,
password=password,
output_encoding=output_encoding)
except Exception as exc:
ret['comment'] = six.text_type(exc)
return ret
else:
need_clone = False
comments = []
def _clone_changes(ret):
ret['changes']['new'] = name + ' => ' + target
def _branch_changes(ret, old, new):
ret['changes']['branch'] = {'old': old, 'new': new}
if need_clone:
if __opts__['test']:
_clone_changes(ret)
comment = '{0} would be cloned to {1}{2}'.format(
name,
target,
' with branch \'{0}\''.format(branch)
if branch is not None
else ''
)
return _neutral_test(ret, comment)
clone_opts = ['--branch', branch] if branch is not None else None
try:
__salt__['git.clone'](target,
name,
opts=clone_opts,
user=user,
password=password,
identity=identity,
https_user=https_user,
https_pass=https_pass,
output_encoding=output_encoding)
except CommandExecutionError as exc:
msg = 'Clone failed: {0}'.format(_strip_exc(exc))
return _fail(ret, msg, comments)
comments.append(
'{0} cloned to {1}{2}'.format(
name,
target,
' with branch \'{0}\''.format(branch)
if branch is not None
else ''
)
)
_clone_changes(ret)
ret['comment'] = _format_comments(comments)
ret['result'] = True
return ret
else:
if branch is None:
return _already_cloned(ret, target, branch, comments)
else:
current_branch = __salt__['git.current_branch'](
target,
user=user,
password=password,
output_encoding=output_encoding)
if current_branch == branch:
return _already_cloned(ret, target, branch, comments)
else:
if __opts__['test']:
_branch_changes(ret, current_branch, branch)
return _neutral_test(
ret,
'Branch would be changed to \'{0}\''.format(branch))
try:
__salt__['git.rev_parse'](
target,
rev=branch,
user=user,
password=password,
ignore_retcode=True,
output_encoding=output_encoding)
except CommandExecutionError:
# Local head does not exist, so we need to check out a new
# branch at the remote rev
checkout_rev = '/'.join(('origin', branch))
checkout_opts = ['-b', branch]
else:
# Local head exists, so we just need to check it out
checkout_rev = branch
checkout_opts = None
try:
__salt__['git.checkout'](
target,
rev=checkout_rev,
opts=checkout_opts,
user=user,
password=password,
output_encoding=output_encoding)
except CommandExecutionError as exc:
msg = 'Failed to change branch to \'{0}\': {1}'.format(branch, exc)
return _fail(ret, msg, comments)
else:
comments.append('Branch changed to \'{0}\''.format(branch))
_branch_changes(ret, current_branch, branch)
ret['comment'] = _format_comments(comments)
ret['result'] = True
return ret
def config_unset(name,
value_regex=None,
repo=None,

View file

@ -448,7 +448,26 @@ def present(name,
# hash_password is True, then hash it.
if password and hash_password:
log.debug('Hashing a clear text password')
password = __salt__['shadow.gen_password'](password)
# in case a password is already set, it will contain a Salt
# which should be re-used to generate the new hash, other-
# wise the Salt will be generated randomly, causing the
# hash to change each time and thereby making the
# user.present state non-idempotent.
algorithms = {
'1': 'md5',
'2a': 'blowfish',
'5': 'sha256',
'6': 'sha512',
}
try:
_, algo, shadow_salt, shadow_hash = __salt__['shadow.info'](name)['passwd'].split('$', 4)
if algo == '1':
log.warning('Using MD5 for hashing passwords is considered insecure!')
log.debug('Re-using existing shadow salt for hashing password using {}'.format(algorithms.get(algo)))
password = __salt__['shadow.gen_password'](password, crypt_salt=shadow_salt, algorithm=algorithms.get(algo))
except ValueError:
log.info('No existing shadow salt found, defaulting to a randomly generated new one')
password = __salt__['shadow.gen_password'](password)
if fullname is not None:
fullname = salt.utils.data.decode(fullname)

View file

@ -6,16 +6,15 @@ Generate the salt thin tarball from the installed python files
# Import python libs
from __future__ import absolute_import, print_function, unicode_literals
import os
import sys
import copy
import shutil
import tarfile
import zipfile
import tempfile
import subprocess
import salt.utils.stringutils
import logging
import os
import shutil
import subprocess
import sys
import tarfile
import tempfile
import zipfile
# Import third party libs
import jinja2

View file

@ -84,15 +84,16 @@ class GitTest(ModuleCase, SaltReturnAssertsMixin):
Validate the git state
'''
def setUp(self):
self.__domain = 'github.com'
domain = 'github.com'
self.test_repo = 'https://{0}/saltstack/salt-test-repo.git'.format(domain)
try:
if hasattr(socket, 'setdefaulttimeout'):
# 10 second dns timeout
socket.setdefaulttimeout(10)
socket.gethostbyname(self.__domain)
socket.gethostbyname(domain)
except socket.error:
msg = 'error resolving {0}, possible network issue?'
self.skipTest(msg.format(self.__domain))
self.skipTest(msg.format(domain))
def tearDown(self):
# Reset the dns timeout after the test is over
@ -105,7 +106,7 @@ class GitTest(ModuleCase, SaltReturnAssertsMixin):
'''
ret = self.run_state(
'git.latest',
name='https://{0}/saltstack/salt-test-repo.git'.format(self.__domain),
name=self.test_repo,
target=target
)
self.assertSaltTrueReturn(ret)
@ -118,7 +119,7 @@ class GitTest(ModuleCase, SaltReturnAssertsMixin):
'''
ret = self.run_state(
'git.latest',
name='https://{0}/saltstack/salt-test-repo.git'.format(self.__domain),
name=self.test_repo,
rev='develop',
target=target,
submodules=True
@ -148,7 +149,7 @@ class GitTest(ModuleCase, SaltReturnAssertsMixin):
'''
ret = self.run_state(
'git.latest',
name='https://{0}/saltstack/salt-test-repo.git'.format(self.__domain),
name=self.test_repo,
rev='develop',
target=target,
submodules=True
@ -164,7 +165,7 @@ class GitTest(ModuleCase, SaltReturnAssertsMixin):
'''
ret = self.run_state(
'git.latest',
name='https://{0}/saltstack/salt-test-repo.git'.format(self.__domain),
name=self.test_repo,
rev='develop',
target=target,
unless='test -e {0}'.format(target),
@ -180,7 +181,7 @@ class GitTest(ModuleCase, SaltReturnAssertsMixin):
'''
ret = self.run_state(
'git.latest',
name='https://{0}/saltstack/salt-test-repo.git'.format(self.__domain),
name=self.test_repo,
rev=0.11,
target=target,
submodules=True,
@ -198,7 +199,7 @@ class GitTest(ModuleCase, SaltReturnAssertsMixin):
# Clone repo
ret = self.run_state(
'git.latest',
name='https://{0}/saltstack/salt-test-repo.git'.format(self.__domain),
name=self.test_repo,
target=target
)
self.assertSaltTrueReturn(ret)
@ -214,7 +215,7 @@ class GitTest(ModuleCase, SaltReturnAssertsMixin):
# Re-run state with force_reset=False
ret = self.run_state(
'git.latest',
name='https://{0}/saltstack/salt-test-repo.git'.format(self.__domain),
name=self.test_repo,
target=target,
force_reset=False
)
@ -229,7 +230,7 @@ class GitTest(ModuleCase, SaltReturnAssertsMixin):
# Now run the state with force_reset=True
ret = self.run_state(
'git.latest',
name='https://{0}/saltstack/salt-test-repo.git'.format(self.__domain),
name=self.test_repo,
target=target,
force_reset=True
)
@ -250,12 +251,11 @@ class GitTest(ModuleCase, SaltReturnAssertsMixin):
def _head(cwd):
return self.run_function('git.rev_parse', [cwd, 'HEAD'])
repo_url = 'https://{0}/saltstack/salt-test-repo.git'.format(self.__domain)
mirror_url = 'file://' + mirror_dir
# Mirror the repo
self.run_function(
'git.clone', [mirror_dir], url=repo_url, opts='--mirror')
'git.clone', [mirror_dir], url=self.test_repo, opts='--mirror')
# Make sure the directory for the mirror now exists
self.assertTrue(os.path.exists(mirror_dir))
@ -303,7 +303,7 @@ class GitTest(ModuleCase, SaltReturnAssertsMixin):
# Clone repo
ret = self.run_state(
'git.latest',
name='https://{0}/saltstack/salt-test-repo.git'.format(self.__domain),
name=self.test_repo,
rev=rev,
target=target
)
@ -324,7 +324,7 @@ class GitTest(ModuleCase, SaltReturnAssertsMixin):
# comment field.
ret = self.run_state(
'git.latest',
name='https://{0}/saltstack/salt-test-repo.git'.format(self.__domain),
name=self.test_repo,
rev=rev,
target=target
)
@ -413,7 +413,7 @@ class GitTest(ModuleCase, SaltReturnAssertsMixin):
'''
ret = self.run_state(
'git.latest',
name='https://{0}/saltstack/salt-test-repo.git'.format(self.__domain),
name=self.test_repo,
rev='HEAD',
target=target,
depth=1
@ -427,7 +427,7 @@ class GitTest(ModuleCase, SaltReturnAssertsMixin):
ret = self.run_state(
'git.latest',
name='https://{0}/saltstack/salt-test-repo.git'.format(self.__domain),
name=self.test_repo,
rev='non-default-branch',
target=target,
depth=1
@ -435,6 +435,235 @@ class GitTest(ModuleCase, SaltReturnAssertsMixin):
self.assertSaltTrueReturn(ret)
self.assertTrue(os.path.isdir(os.path.join(target, '.git')))
@with_tempdir(create=False)
def test_cloned(self, target):
'''
Test git.cloned state
'''
# Test mode
ret = self.run_state(
'git.cloned',
name=self.test_repo,
target=target,
test=True)
ret = ret[next(iter(ret))]
assert ret['result'] is None
assert ret['changes'] == {
'new': '{0} => {1}'.format(self.test_repo, target)
}
assert ret['comment'] == '{0} would be cloned to {1}'.format(
self.test_repo,
target
)
# Now actually run the state
ret = self.run_state(
'git.cloned',
name=self.test_repo,
target=target)
ret = ret[next(iter(ret))]
assert ret['result'] is True
assert ret['changes'] == {
'new': '{0} => {1}'.format(self.test_repo, target)
}
assert ret['comment'] == '{0} cloned to {1}'.format(self.test_repo, target)
# Run the state again to test idempotence
ret = self.run_state(
'git.cloned',
name=self.test_repo,
target=target)
ret = ret[next(iter(ret))]
assert ret['result'] is True
assert not ret['changes']
assert ret['comment'] == 'Repository already exists at {0}'.format(target)
# Run the state again to test idempotence (test mode)
ret = self.run_state(
'git.cloned',
name=self.test_repo,
target=target,
test=True)
ret = ret[next(iter(ret))]
assert not ret['changes']
assert ret['result'] is True
assert ret['comment'] == 'Repository already exists at {0}'.format(target)
@with_tempdir(create=False)
def test_cloned_with_branch(self, target):
'''
Test git.cloned state with branch provided
'''
old_branch = 'master'
new_branch = 'develop'
bad_branch = 'thisbranchdoesnotexist'
# Test mode
ret = self.run_state(
'git.cloned',
name=self.test_repo,
target=target,
branch=old_branch,
test=True)
ret = ret[next(iter(ret))]
assert ret['result'] is None
assert ret['changes'] == {
'new': '{0} => {1}'.format(self.test_repo, target)
}
assert ret['comment'] == (
'{0} would be cloned to {1} with branch \'{2}\''.format(
self.test_repo,
target,
old_branch
)
)
# Now actually run the state
ret = self.run_state(
'git.cloned',
name=self.test_repo,
target=target,
branch=old_branch)
ret = ret[next(iter(ret))]
assert ret['result'] is True
assert ret['changes'] == {
'new': '{0} => {1}'.format(self.test_repo, target)
}
assert ret['comment'] == (
'{0} cloned to {1} with branch \'{2}\''.format(
self.test_repo,
target,
old_branch
)
)
# Run the state again to test idempotence
ret = self.run_state(
'git.cloned',
name=self.test_repo,
target=target,
branch=old_branch)
ret = ret[next(iter(ret))]
assert ret['result'] is True
assert not ret['changes']
assert ret['comment'] == (
'Repository already exists at {0} '
'and is checked out to branch \'{1}\''.format(target, old_branch)
)
# Run the state again to test idempotence (test mode)
ret = self.run_state(
'git.cloned',
name=self.test_repo,
target=target,
test=True,
branch=old_branch)
ret = ret[next(iter(ret))]
assert ret['result'] is True
assert not ret['changes']
assert ret['comment'] == (
'Repository already exists at {0} '
'and is checked out to branch \'{1}\''.format(target, old_branch)
)
# Change branch (test mode)
ret = self.run_state(
'git.cloned',
name=self.test_repo,
target=target,
branch=new_branch,
test=True)
ret = ret[next(iter(ret))]
assert ret['result'] is None
assert ret['changes'] == {
'branch': {'old': old_branch, 'new': new_branch}
}
assert ret['comment'] == 'Branch would be changed to \'{0}\''.format(
new_branch
)
# Now really change the branch
ret = self.run_state(
'git.cloned',
name=self.test_repo,
target=target,
branch=new_branch)
ret = ret[next(iter(ret))]
assert ret['result'] is True
assert ret['changes'] == {
'branch': {'old': old_branch, 'new': new_branch}
}
assert ret['comment'] == 'Branch changed to \'{0}\''.format(
new_branch
)
# Change back to original branch. This tests that we don't attempt to
# checkout a new branch (i.e. git checkout -b) for a branch that exists
# locally, as that would fail.
ret = self.run_state(
'git.cloned',
name=self.test_repo,
target=target,
branch=old_branch)
ret = ret[next(iter(ret))]
assert ret['result'] is True
assert ret['changes'] == {
'branch': {'old': new_branch, 'new': old_branch}
}
assert ret['comment'] == 'Branch changed to \'{0}\''.format(
old_branch
)
# Test switching to a nonexistant branch. This should fail.
ret = self.run_state(
'git.cloned',
name=self.test_repo,
target=target,
branch=bad_branch)
ret = ret[next(iter(ret))]
assert ret['result'] is False
assert not ret['changes']
assert ret['comment'].startswith(
'Failed to change branch to \'{0}\':'.format(bad_branch)
)
@with_tempdir(create=False)
def test_cloned_with_nonexistant_branch(self, target):
'''
Test git.cloned state with a nonexistant branch provided
'''
branch = 'thisbranchdoesnotexist'
# Test mode
ret = self.run_state(
'git.cloned',
name=self.test_repo,
target=target,
branch=branch,
test=True)
ret = ret[next(iter(ret))]
assert ret['result'] is None
assert ret['changes']
assert ret['comment'] == (
'{0} would be cloned to {1} with branch \'{2}\''.format(
self.test_repo,
target,
branch
)
)
# Now actually run the state
ret = self.run_state(
'git.cloned',
name=self.test_repo,
target=target,
branch=branch)
ret = ret[next(iter(ret))]
assert ret['result'] is False
assert not ret['changes']
assert ret['comment'].startswith('Clone failed:')
assert 'not found in upstream origin' in ret['comment']
@with_tempdir(create=False)
def test_present(self, name):
'''

View file

@ -0,0 +1,54 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import
import tarfile
import tempfile
import subprocess
import sys
import os
from tests.support.unit import TestCase
import salt.utils.files
import salt.utils.thin
try:
import virtualenv
HAS_VENV = True
except ImportError:
HAS_VENV = False
class TestThinDir(TestCase):
def setUp(self):
self.tmpdir = tempfile.mkdtemp()
def tearDown(self):
salt.utils.files.rm_rf(self.tmpdir)
def test_thin_dir(self):
'''
Test the thin dir to make sure salt-call can run
Run salt call via a python in a new virtual environment to ensure
salt-call has all dependencies needed.
'''
venv_dir = os.path.join(self.tmpdir, 'venv')
virtualenv.create_environment(venv_dir)
salt.utils.thin.gen_thin(self.tmpdir)
thin_dir = os.path.join(self.tmpdir, 'thin')
thin_archive = os.path.join(thin_dir, 'thin.tgz')
tar = tarfile.open(thin_archive)
tar.extractall(thin_dir)
tar.close()
bins = 'bin'
if sys.platform == 'win32':
bins = 'Scripts'
cmd = [
os.path.join(venv_dir, bins, 'python'),
os.path.join(thin_dir, 'salt-call'),
'--version',
]
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = proc.communicate()
proc.wait()
assert proc.returncode == 0, (stdout, stderr, proc.returncode)

View file

@ -16,6 +16,7 @@ from tests.support.mixins import LoaderModuleMockMixin
from tests.support.paths import TMP, TMP_CONF_DIR
from tests.support.unit import TestCase, skipIf
from tests.support.mock import (
Mock,
MagicMock,
patch,
mock_open,
@ -358,6 +359,7 @@ class StateTestCase(TestCase, LoaderModuleMockMixin):
'__utils__': utils,
'__salt__': {
'config.get': config.get,
'config.option': MagicMock(return_value=''),
}
},
config: {
@ -812,28 +814,25 @@ class StateTestCase(TestCase, LoaderModuleMockMixin):
"whitelist=sls1.sls",
pillar="A")
mock = MagicMock(return_value=True)
with patch.dict(state.__salt__,
{'config.option': mock}):
mock = MagicMock(return_value="A")
mock = MagicMock(return_value="A")
with patch.object(state, '_filter_running',
mock):
mock = MagicMock(return_value=True)
with patch.object(state, '_filter_running',
mock):
mock = MagicMock(return_value=True)
with patch.object(state, '_filter_running',
with patch.object(salt.payload, 'Serial',
mock):
mock = MagicMock(return_value=True)
with patch.object(salt.payload, 'Serial',
mock):
with patch.object(os.path,
'join', mock):
with patch.object(
state,
'_set'
'_retcode',
mock):
self.assertTrue(state.
highstate
(arg))
with patch.object(os.path,
'join', mock):
with patch.object(
state,
'_set'
'_retcode',
mock):
self.assertTrue(state.
highstate
(arg))
def test_clear_request(self):
'''
@ -974,17 +973,11 @@ class StateTestCase(TestCase, LoaderModuleMockMixin):
MockState.HighState.flag = False
mock = MagicMock(return_value=True)
with patch.dict(state.__salt__,
{'config.option':
mock}):
mock = MagicMock(return_value=
True)
with patch.object(
state,
'_filter_'
'running',
mock):
self.sub_test_sls()
with patch.object(state,
'_filter_'
'running',
mock):
self.sub_test_sls()
def test_get_test_value(self):
'''
@ -1067,6 +1060,75 @@ class StateTestCase(TestCase, LoaderModuleMockMixin):
None,
True))
def test_sls_sync(self):
'''
Test test.sls with the sync argument
We're only mocking the sync functions we expect to sync. If any other
sync functions are run then they will raise a KeyError, which we want
as it will tell us that we are syncing things we shouldn't.
'''
mock_empty_list = MagicMock(return_value=[])
with patch.object(state, 'running', mock_empty_list), \
patch.object(state, '_disabled', mock_empty_list), \
patch.object(state, '_get_pillar_errors', mock_empty_list):
sync_mocks = {
'saltutil.sync_modules': Mock(),
'saltutil.sync_states': Mock(),
}
with patch.dict(state.__salt__, sync_mocks):
state.sls('foo', sync_mods='modules,states')
for key in sync_mocks:
call_count = sync_mocks[key].call_count
expected = 1
assert call_count == expected, \
'{0} called {1} time(s) (expected: {2})'.format(
key, call_count, expected
)
# Test syncing all
sync_mocks = {'saltutil.sync_all': Mock()}
with patch.dict(state.__salt__, sync_mocks):
state.sls('foo', sync_mods='all')
for key in sync_mocks:
call_count = sync_mocks[key].call_count
expected = 1
assert call_count == expected, \
'{0} called {1} time(s) (expected: {2})'.format(
key, call_count, expected
)
# sync_mods=True should be interpreted as sync_mods=all
sync_mocks = {'saltutil.sync_all': Mock()}
with patch.dict(state.__salt__, sync_mocks):
state.sls('foo', sync_mods=True)
for key in sync_mocks:
call_count = sync_mocks[key].call_count
expected = 1
assert call_count == expected, \
'{0} called {1} time(s) (expected: {2})'.format(
key, call_count, expected
)
# Test syncing all when "all" is passed along with module types.
# This tests that we *only* run a sync_all and avoid unnecessary
# extra syncing.
sync_mocks = {'saltutil.sync_all': Mock()}
with patch.dict(state.__salt__, sync_mocks):
state.sls('foo', sync_mods='modules,all')
for key in sync_mocks:
call_count = sync_mocks[key].call_count
expected = 1
assert call_count == expected, \
'{0} called {1} time(s) (expected: {2})'.format(
key, call_count, expected
)
def test_pkg(self):
'''
Test to execute a packaged state run