mirror of
https://github.com/saltstack/salt.git
synced 2025-04-17 10:10:20 +00:00
Merge branch '2018.3' into 'develop'
Conflicts: - salt/client/ssh/__init__.py - salt/utils/thin.py
This commit is contained in:
commit
bf6c788350
34 changed files with 6132 additions and 121 deletions
|
@ -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' %>
|
||||
|
|
23
doc/faq.rst
23
doc/faq.rst
|
@ -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
|
||||
|
|
|
@ -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
|
||||
.
|
||||
|
|
|
@ -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
|
||||
.
|
||||
|
|
|
@ -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
|
||||
.
|
||||
|
|
|
@ -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
|
||||
.
|
||||
|
|
|
@ -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
|
||||
.
|
||||
|
|
|
@ -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
|
||||
.
|
||||
|
|
|
@ -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
|
||||
.
|
||||
|
|
|
@ -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
|
||||
.
|
||||
|
|
|
@ -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
|
||||
.
|
||||
|
|
|
@ -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
|
||||
.
|
||||
|
|
|
@ -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
|
||||
.
|
||||
|
|
|
@ -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
|
||||
.
|
||||
|
|
|
@ -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
|
||||
.
|
||||
|
|
5348
doc/man/salt.7
5348
doc/man/salt.7
File diff suppressed because it is too large
Load diff
|
@ -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
|
||||
.
|
||||
|
|
|
@ -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'),
|
||||
]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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::
|
||||
|
||||
|
|
|
@ -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>`
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
'''
|
||||
|
|
54
tests/integration/utils/test_thin.py
Normal file
54
tests/integration/utils/test_thin.py
Normal 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)
|
|
@ -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
|
||||
|
|
Loading…
Add table
Reference in a new issue