Merge branch '2016.11' into 2016.11

This commit is contained in:
Daniel Wallace 2017-12-04 10:13:15 -07:00 committed by GitHub
commit 1d0bd5bb32
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
34 changed files with 1482 additions and 830 deletions

View file

@ -5,7 +5,7 @@
<% driverfile = ENV['SALT_KITCHEN_DRIVER'] || '.kitchen/driver.yml' %>
<% if File.exists?(driverfile) %>
<%= File.read(driverfile) %>
<%= ERB.new(File.read(driverfile)).result %>
<% else %>
driver:
name: docker
@ -31,7 +31,7 @@ provisioner:
log_level: info
require_chef: false
remote_states:
name: git://github.com/gtmanfred/salt-jenkins.git
name: git://github.com/saltstack/salt-jenkins.git
branch: 2016.11
repo: git
testingdir: /testing
@ -51,7 +51,7 @@ provisioner:
- git.salt
- kitchen
<% if File.exists?(platformsfile) %>
<%= File.read(platformsfile) %>
<%= ERB.new(File.read(platformsfile)).result %>
<% else %>
platforms:
- name: fedora

View file

@ -19,14 +19,18 @@ Salt SSH allows for salt routines to be executed using only SSH for transport
Options
=======
.. program:: salt-ssh
.. include:: _includes/common-options.rst
.. option:: --hard-crash
Raise any original exception rather than exiting gracefully. Default: False.
.. option:: -r, --raw, --raw-shell
Execute a raw shell command.
.. option:: --priv
Specify the SSH private key file to be used for authentication.
.. option:: --roster
Define which roster system to use, this defines if a database backend,
@ -53,38 +57,117 @@ Options
the more running process the faster communication should be, default
is 25.
.. option:: --extra-filerefs=EXTRA_FILEREFS
Pass in extra files to include in the state tarball.
.. option:: --min-extra-modules=MIN_EXTRA_MODS
One or comma-separated list of extra Python modulesto be included
into Minimal Salt.
.. option:: --thin-extra-modules=THIN_EXTRA_MODS
One or comma-separated list of extra Python modulesto be included
into Thin Salt.
.. option:: -v, --verbose
Turn on command verbosity, display jid.
.. option:: -s, --static
Return the data from minions as a group after they all return.
.. option:: -w, --wipe
Remove the deployment of the salt files when done executing.
.. option:: -W, --rand-thin-dir
Select a random temp dir to deploy on the remote system. The dir
will be cleaned after the execution.
.. option:: -t, --regen-thin, --thin
Trigger a thin tarball regeneration. This is needed if custom
grains/modules/states have been added or updated.
.. option:: --python2-bin=PYTHON2_BIN
Path to a python2 binary which has salt installed.
.. option:: --python3-bin=PYTHON3_BIN
Path to a python3 binary which has salt installed.
.. option:: --jid=JID
Pass a JID to be used instead of generating one.
Authentication Options
----------------------
.. option:: --priv=SSH_PRIV
Specify the SSH private key file to be used for authentication.
.. option:: -i, --ignore-host-keys
Disables StrictHostKeyChecking to relax acceptance of new and unknown
host keys.
By default ssh host keys are honored and connections will ask for
approval. Use this option to disable StrictHostKeyChecking.
.. option:: --no-host-keys
Fully ignores ssh host keys which by default are honored and connections
would ask for approval. Useful if the host key of a remote server has
would ask for approval. Useful if the host key of a remote server has
changed and would still error with --ignore-host-keys.
.. option:: --user=SSH_USER
Set the default user to attempt to use when authenticating.
.. option:: --passwd
Set the default password to attempt to use when authenticating.
.. option:: --askpass
Interactively ask for the SSH password with no echo - avoids password
in process args and stored in history.
.. option:: --key-deploy
Set this flag to attempt to deploy the authorized ssh key with all
minions. This combined with --passwd can make initial deployment of keys
very fast and easy.
.. program:: salt
.. option:: --identities-only
.. include:: _includes/common-options.rst
Use the only authentication identity files configured in the ssh_config
files. See IdentitiesOnly flag in man ssh_config.
.. include:: _includes/target-selection-ssh.rst
.. option:: --sudo
Run command via sudo.
Scan Roster Options
-------------------
.. option:: --scan-ports=SSH_SCAN_PORTS
Comma-separated list of ports to scan in the scan roster.
.. option:: --scan-timeout=SSH_SCAN_TIMEOUT
Scanning socket timeout for the scan roster.
.. include:: _includes/logging-options.rst
.. |logfile| replace:: /var/log/salt/ssh
.. |loglevel| replace:: ``warning``
.. include:: _includes/target-selection-ssh.rst
.. include:: _includes/output-options.rst

View file

@ -225,15 +225,16 @@ enclosing brackets ``[`` and ``]``:
Default: ``{}``
This can be used to control logging levels more specifically. The example sets
the main salt library at the 'warning' level, but sets ``salt.modules`` to log
at the ``debug`` level:
This can be used to control logging levels more specifically, based on log call name. The example sets
the main salt library at the 'warning' level, sets ``salt.modules`` to log
at the ``debug`` level, and sets a custom module to the ``all`` level:
.. code-block:: yaml
log_granular_levels:
'salt': 'warning'
'salt.modules': 'debug'
'salt.loader.saltmaster.ext.module.custom_module': 'all'
External Logging Handlers
-------------------------

View file

@ -303,6 +303,20 @@ option on the Salt master.
master_port: 4506
.. conf_minion:: publish_port
``publish_port``
---------------
Default: ``4505``
The port of the master publish server, this needs to coincide with the publish_port
option on the Salt master.
.. code-block:: yaml
publish_port: 4505
.. conf_minion:: user
``user``

View file

@ -298,3 +298,9 @@ Syncing grains can be done a number of ways, they are automatically synced when
above) the grains can be manually synced and reloaded by calling the
:mod:`saltutil.sync_grains <salt.modules.saltutil.sync_grains>` or
:mod:`saltutil.sync_all <salt.modules.saltutil.sync_all>` functions.
.. note::
When the :conf_minion:`grains_cache` is set to False, the grains dictionary is built
and stored in memory on the minion. Every time the minion restarts or
``saltutil.refresh_grains`` is run, the grain dictionary is rebuilt from scratch.

View file

@ -237,7 +237,7 @@ class SyncClientMixin(object):
def low(self, fun, low, print_event=True, full_return=False):
'''
Check for deprecated usage and allow until Salt Oxygen.
Check for deprecated usage and allow until Salt Fluorine.
'''
msg = []
if 'args' in low:
@ -248,7 +248,7 @@ class SyncClientMixin(object):
low['kwarg'] = low.pop('kwargs')
if msg:
salt.utils.warn_until('Oxygen', ' '.join(msg))
salt.utils.warn_until('Fluorine', ' '.join(msg))
return self._low(fun, low, print_event=print_event, full_return=full_return)

View file

@ -451,14 +451,14 @@ def tar(options, tarfile, sources=None, dest=None,
.. code-block:: bash
salt '*' archive.tar -cjvf /tmp/salt.tar.bz2 {{grains.saltpath}} template=jinja
salt '*' archive.tar cjvf /tmp/salt.tar.bz2 {{grains.saltpath}} template=jinja
CLI Examples:
.. code-block:: bash
# Create a tarfile
salt '*' archive.tar -cjvf /tmp/tarfile.tar.bz2 /tmp/file_1,/tmp/file_2
salt '*' archive.tar cjvf /tmp/tarfile.tar.bz2 /tmp/file_1,/tmp/file_2
# Unpack a tarfile
salt '*' archive.tar xf foo.tar dest=/target/directory
'''

View file

@ -1856,14 +1856,14 @@ def line(path, content, match=None, mode=None, location=None,
if changed:
if show_changes:
with salt.utils.fopen(path, 'r') as fp_:
path_content = _splitlines_preserving_trailing_newline(
fp_.read())
changes_diff = ''.join(difflib.unified_diff(
path_content, _splitlines_preserving_trailing_newline(body)))
path_content = fp_.read().splitlines(True)
changes_diff = ''.join(difflib.unified_diff(path_content, body.splitlines(True)))
if __opts__['test'] is False:
fh_ = None
try:
fh_ = salt.utils.atomicfile.atomic_open(path, 'w')
# Make sure we match the file mode from salt.utils.fopen
mode = 'wb' if six.PY2 and salt.utils.is_windows() else 'w'
fh_ = salt.utils.atomicfile.atomic_open(path, mode)
fh_.write(body)
finally:
if fh_:
@ -3764,8 +3764,15 @@ def get_managed(
parsed_scheme = urlparsed_source.scheme
parsed_path = os.path.join(
urlparsed_source.netloc, urlparsed_source.path).rstrip(os.sep)
unix_local_source = parsed_scheme in ('file', '')
if parsed_scheme and parsed_scheme.lower() in 'abcdefghijklmnopqrstuvwxyz':
if unix_local_source:
sfn = parsed_path
if not os.path.exists(sfn):
msg = 'Local file source {0} does not exist'.format(sfn)
return '', {}, msg
if parsed_scheme and parsed_scheme.lower() in string.ascii_lowercase:
parsed_path = ':'.join([parsed_scheme, parsed_path])
parsed_scheme = 'file'
@ -3773,9 +3780,10 @@ def get_managed(
source_sum = __salt__['cp.hash_file'](source, saltenv)
if not source_sum:
return '', {}, 'Source file {0} not found'.format(source)
elif not source_hash and parsed_scheme == 'file':
elif not source_hash and unix_local_source:
source_sum = _get_local_file_source_sum(parsed_path)
elif not source_hash and source.startswith(os.sep):
# This should happen on Windows
source_sum = _get_local_file_source_sum(source)
else:
if not skip_verify:
@ -4650,21 +4658,22 @@ def manage_file(name,
if source_sum and ('hsum' in source_sum):
source_sum['hsum'] = source_sum['hsum'].lower()
if source and not sfn:
# File is not present, cache it
sfn = __salt__['cp.cache_file'](source, saltenv)
if source:
if not sfn:
return _error(
ret, 'Source file \'{0}\' not found'.format(source))
htype = source_sum.get('hash_type', __opts__['hash_type'])
# Recalculate source sum now that file has been cached
source_sum = {
'hash_type': htype,
'hsum': get_hash(sfn, form=htype)
}
# File is not present, cache it
sfn = __salt__['cp.cache_file'](source, saltenv)
if not sfn:
return _error(
ret, 'Source file \'{0}\' not found'.format(source))
htype = source_sum.get('hash_type', __opts__['hash_type'])
# Recalculate source sum now that file has been cached
source_sum = {
'hash_type': htype,
'hsum': get_hash(sfn, form=htype)
}
if keep_mode:
if _urlparse(source).scheme in ('salt', 'file') \
or source.startswith('/'):
if _urlparse(source).scheme in ('salt', 'file', ''):
try:
mode = __salt__['cp.stat_file'](source, saltenv=saltenv, octal=True)
except Exception as exc:
@ -4694,7 +4703,7 @@ def manage_file(name,
# source, and we are not skipping checksum verification, then
# verify that it matches the specified checksum.
if not skip_verify \
and _urlparse(source).scheme not in ('salt', ''):
and _urlparse(source).scheme != 'salt':
dl_sum = get_hash(sfn, source_sum['hash_type'])
if dl_sum != source_sum['hsum']:
ret['comment'] = (
@ -4859,8 +4868,6 @@ def manage_file(name,
group=group, mode=dir_mode)
if source:
# It is a new file, set the diff accordingly
ret['changes']['diff'] = 'New file'
# Apply the new file
if not sfn:
sfn = __salt__['cp.cache_file'](source, saltenv)
@ -4884,6 +4891,8 @@ def manage_file(name,
)
ret['result'] = False
return ret
# It is a new file, set the diff accordingly
ret['changes']['diff'] = 'New file'
if not os.path.isdir(contain_dir):
if makedirs:
_set_mode_and_make_dirs(name, dir_mode, mode, user, group)

View file

@ -24,7 +24,7 @@ Values or Entries
Values/Entries are name/data pairs. There can be many values in a key. The
(Default) value corresponds to the Key, the rest are their own value pairs.
:depends: - winreg Python module
:depends: - PyWin32
'''
# When production windows installer is using Python 3, Python 2 code can be removed
@ -35,14 +35,13 @@ from __future__ import unicode_literals
import sys
import logging
from salt.ext.six.moves import range # pylint: disable=W0622,import-error
from salt.ext import six
# Import third party libs
try:
from salt.ext.six.moves import winreg as _winreg # pylint: disable=import-error,no-name-in-module
from win32gui import SendMessageTimeout
from win32con import HWND_BROADCAST, WM_SETTINGCHANGE, SMTO_ABORTIFHUNG
from win32api import RegCreateKeyEx, RegSetValueEx, RegFlushKey, RegCloseKey, error as win32apiError
import win32gui
import win32api
import win32con
import pywintypes
HAS_WINDOWS_MODULES = True
except ImportError:
HAS_WINDOWS_MODULES = False
@ -60,7 +59,7 @@ __virtualname__ = 'reg'
def __virtual__():
'''
Only works on Windows systems with the _winreg python module
Only works on Windows systems with the PyWin32
'''
if not salt.utils.is_windows():
return (False, 'reg execution module failed to load: '
@ -69,104 +68,76 @@ def __virtual__():
if not HAS_WINDOWS_MODULES:
return (False, 'reg execution module failed to load: '
'One of the following libraries did not load: '
+ '_winreg, win32gui, win32con, win32api')
+ 'win32gui, win32con, win32api')
return __virtualname__
# winreg in python 2 is hard coded to use codex 'mbcs', which uses
# encoding that the user has assign. The function _unicode_to_mbcs
# and _unicode_to_mbcs help with this.
def _to_mbcs(vdata):
'''
Converts unicode to to current users character encoding. Use this for values
returned by reg functions
'''
return salt.utils.to_unicode(vdata, 'mbcs')
def _unicode_to_mbcs(instr):
def _to_unicode(vdata):
'''
Converts unicode to to current users character encoding.
Converts from current users character encoding to unicode. Use this for
parameters being pass to reg functions
'''
if isinstance(instr, six.text_type):
# unicode to windows utf8
return instr.encode('mbcs')
else:
# Assume its byte str or not a str/unicode
return instr
def _mbcs_to_unicode(instr):
'''
Converts from current users character encoding to unicode.
When instr has a value of None, the return value of the function
will also be None.
'''
if instr is None or isinstance(instr, six.text_type):
return instr
else:
return six.text_type(instr, 'mbcs')
def _mbcs_to_unicode_wrap(obj, vtype):
'''
Wraps _mbcs_to_unicode for use with registry vdata
'''
if vtype == 'REG_BINARY':
# We should be able to leave it alone if the user has passed binary data in yaml with
# binary !!
# In python < 3 this should have type str and in python 3+ this should be a byte array
return obj
if isinstance(obj, list):
return [_mbcs_to_unicode(x) for x in obj]
elif isinstance(obj, six.integer_types):
return obj
else:
return _mbcs_to_unicode(obj)
return salt.utils.to_unicode(vdata, 'utf-8')
class Registry(object): # pylint: disable=R0903
'''
Delay '_winreg' usage until this module is used
Delay usage until this module is used
'''
def __init__(self):
self.hkeys = {
'HKEY_CURRENT_USER': _winreg.HKEY_CURRENT_USER,
'HKEY_LOCAL_MACHINE': _winreg.HKEY_LOCAL_MACHINE,
'HKEY_USERS': _winreg.HKEY_USERS,
'HKCU': _winreg.HKEY_CURRENT_USER,
'HKLM': _winreg.HKEY_LOCAL_MACHINE,
'HKU': _winreg.HKEY_USERS,
'HKEY_CURRENT_USER': win32con.HKEY_CURRENT_USER,
'HKEY_LOCAL_MACHINE': win32con.HKEY_LOCAL_MACHINE,
'HKEY_USERS': win32con.HKEY_USERS,
'HKCU': win32con.HKEY_CURRENT_USER,
'HKLM': win32con.HKEY_LOCAL_MACHINE,
'HKU': win32con.HKEY_USERS,
}
self.vtype = {
'REG_BINARY': _winreg.REG_BINARY,
'REG_DWORD': _winreg.REG_DWORD,
'REG_EXPAND_SZ': _winreg.REG_EXPAND_SZ,
'REG_MULTI_SZ': _winreg.REG_MULTI_SZ,
'REG_SZ': _winreg.REG_SZ
'REG_BINARY': win32con.REG_BINARY,
'REG_DWORD': win32con.REG_DWORD,
'REG_EXPAND_SZ': win32con.REG_EXPAND_SZ,
'REG_MULTI_SZ': win32con.REG_MULTI_SZ,
'REG_SZ': win32con.REG_SZ,
'REG_QWORD': win32con.REG_QWORD
}
self.opttype = {
'REG_OPTION_NON_VOLATILE': _winreg.REG_OPTION_NON_VOLATILE,
'REG_OPTION_VOLATILE': _winreg.REG_OPTION_VOLATILE
'REG_OPTION_NON_VOLATILE': 0,
'REG_OPTION_VOLATILE': 1
}
# Return Unicode due to from __future__ import unicode_literals
self.vtype_reverse = {
_winreg.REG_BINARY: 'REG_BINARY',
_winreg.REG_DWORD: 'REG_DWORD',
_winreg.REG_EXPAND_SZ: 'REG_EXPAND_SZ',
_winreg.REG_MULTI_SZ: 'REG_MULTI_SZ',
_winreg.REG_SZ: 'REG_SZ'
win32con.REG_BINARY: 'REG_BINARY',
win32con.REG_DWORD: 'REG_DWORD',
win32con.REG_EXPAND_SZ: 'REG_EXPAND_SZ',
win32con.REG_MULTI_SZ: 'REG_MULTI_SZ',
win32con.REG_SZ: 'REG_SZ',
win32con.REG_QWORD: 'REG_QWORD'
}
self.opttype_reverse = {
_winreg.REG_OPTION_NON_VOLATILE: 'REG_OPTION_NON_VOLATILE',
_winreg.REG_OPTION_VOLATILE: 'REG_OPTION_VOLATILE'
0: 'REG_OPTION_NON_VOLATILE',
1: 'REG_OPTION_VOLATILE'
}
# delete_key_recursive uses this to check the subkey contains enough \
# as we do not want to remove all or most of the registry
self.subkey_slash_check = {
_winreg.HKEY_CURRENT_USER: 0,
_winreg.HKEY_LOCAL_MACHINE: 1,
_winreg.HKEY_USERS: 1
win32con.HKEY_CURRENT_USER: 0,
win32con.HKEY_LOCAL_MACHINE: 1,
win32con.HKEY_USERS: 1
}
self.registry_32 = {
True: _winreg.KEY_READ | _winreg.KEY_WOW64_32KEY,
False: _winreg.KEY_READ,
True: win32con.KEY_READ | win32con.KEY_WOW64_32KEY,
False: win32con.KEY_READ,
}
def __getattr__(self, k):
@ -189,21 +160,16 @@ def _key_exists(hive, key, use_32bit_registry=False):
:return: Returns True if found, False if not found
:rtype: bool
'''
if PY2:
local_hive = _mbcs_to_unicode(hive)
local_key = _unicode_to_mbcs(key)
else:
local_hive = hive
local_key = key
local_hive = _to_unicode(hive)
local_key = _to_unicode(key)
registry = Registry()
hkey = registry.hkeys[local_hive]
access_mask = registry.registry_32[use_32bit_registry]
try:
handle = _winreg.OpenKey(hkey, local_key, 0, access_mask)
_winreg.CloseKey(handle)
handle = win32api.RegOpenKeyEx(hkey, local_key, 0, access_mask)
win32api.RegCloseKey(handle)
return True
except WindowsError: # pylint: disable=E0602
return False
@ -222,8 +188,9 @@ def broadcast_change():
salt '*' reg.broadcast_change
'''
# https://msdn.microsoft.com/en-us/library/windows/desktop/ms644952(v=vs.85).aspx
_, res = SendMessageTimeout(HWND_BROADCAST, WM_SETTINGCHANGE, 0, 0,
SMTO_ABORTIFHUNG, 5000)
_, res = win32gui.SendMessageTimeout(
win32con.HWND_BROADCAST, win32con.WM_SETTINGCHANGE, 0, 0,
win32con.SMTO_ABORTIFHUNG, 5000)
return not bool(res)
@ -253,12 +220,8 @@ def list_keys(hive, key=None, use_32bit_registry=False):
salt '*' reg.list_keys HKLM 'SOFTWARE'
'''
if PY2:
local_hive = _mbcs_to_unicode(hive)
local_key = _unicode_to_mbcs(key)
else:
local_hive = hive
local_key = key
local_hive = _to_unicode(hive)
local_key = _to_unicode(key)
registry = Registry()
hkey = registry.hkeys[local_hive]
@ -266,12 +229,12 @@ def list_keys(hive, key=None, use_32bit_registry=False):
subkeys = []
try:
handle = _winreg.OpenKey(hkey, local_key, 0, access_mask)
handle = win32api.RegOpenKeyEx(hkey, local_key, 0, access_mask)
for i in range(_winreg.QueryInfoKey(handle)[0]):
subkey = _winreg.EnumKey(handle, i)
for i in range(win32api.RegQueryInfoKey(handle)[0]):
subkey = win32api.RegEnumKey(handle, i)
if PY2:
subkeys.append(_mbcs_to_unicode(subkey))
subkeys.append(_to_unicode(subkey))
else:
subkeys.append(subkey)
@ -312,13 +275,8 @@ def list_values(hive, key=None, use_32bit_registry=False, include_default=True):
salt '*' reg.list_values HKLM 'SYSTEM\\CurrentControlSet\\Services\\Tcpip'
'''
if PY2:
local_hive = _mbcs_to_unicode(hive)
local_key = _unicode_to_mbcs(key)
else:
local_hive = hive
local_key = key
local_hive = _to_unicode(hive)
local_key = _to_unicode(key)
registry = Registry()
hkey = registry.hkeys[local_hive]
@ -327,37 +285,21 @@ def list_values(hive, key=None, use_32bit_registry=False, include_default=True):
values = list()
try:
handle = _winreg.OpenKey(hkey, local_key, 0, access_mask)
handle = win32api.RegOpenKeyEx(hkey, local_key, 0, access_mask)
for i in range(_winreg.QueryInfoKey(handle)[1]):
vname, vdata, vtype = _winreg.EnumValue(handle, i)
for i in range(win32api.RegQueryInfoKey(handle)[1]):
vname, vdata, vtype = win32api.RegEnumValue(handle, i)
if not vname:
vname = "(Default)"
value = {'hive': local_hive,
'key': local_key,
'vname': vname,
'vdata': vdata,
'vname': _to_mbcs(vname),
'vdata': _to_mbcs(vdata),
'vtype': registry.vtype_reverse[vtype],
'success': True}
values.append(value)
if include_default:
# Get the default value for the key
value = {'hive': local_hive,
'key': local_key,
'vname': '(Default)',
'vdata': None,
'success': True}
try:
# QueryValueEx returns unicode data
vdata, vtype = _winreg.QueryValueEx(handle, '(Default)')
if vdata or vdata in [0, '']:
value['vtype'] = registry.vtype_reverse[vtype]
value['vdata'] = vdata
else:
value['comment'] = 'Empty Value'
except WindowsError: # pylint: disable=E0602
value['vdata'] = ('(value not set)')
value['vtype'] = 'REG_SZ'
values.append(value)
except WindowsError as exc: # pylint: disable=E0602
log.debug(exc)
log.debug(r'Cannot find key: {0}\{1}'.format(hive, key))
@ -403,30 +345,19 @@ def read_value(hive, key, vname=None, use_32bit_registry=False):
salt '*' reg.read_value HKEY_LOCAL_MACHINE 'SOFTWARE\Salt' 'version'
'''
# If no name is passed, the default value of the key will be returned
# The value name is Default
# Setup the return array
if PY2:
ret = {'hive': _mbcs_to_unicode(hive),
'key': _mbcs_to_unicode(key),
'vname': _mbcs_to_unicode(vname),
'vdata': None,
'success': True}
local_hive = _mbcs_to_unicode(hive)
local_key = _unicode_to_mbcs(key)
local_vname = _unicode_to_mbcs(vname)
local_hive = _to_unicode(hive)
local_key = _to_unicode(key)
local_vname = _to_unicode(vname)
else:
ret = {'hive': hive,
'key': key,
'vname': vname,
'vdata': None,
'success': True}
local_hive = hive
local_key = key
local_vname = vname
ret = {'hive': local_hive,
'key': local_key,
'vname': local_vname,
'vdata': None,
'success': True}
if not vname:
ret['vname'] = '(Default)'
@ -436,19 +367,22 @@ def read_value(hive, key, vname=None, use_32bit_registry=False):
access_mask = registry.registry_32[use_32bit_registry]
try:
handle = _winreg.OpenKey(hkey, local_key, 0, access_mask)
handle = win32api.RegOpenKeyEx(hkey, local_key, 0, access_mask)
try:
# QueryValueEx returns unicode data
vdata, vtype = _winreg.QueryValueEx(handle, local_vname)
# RegQueryValueEx returns and accepts unicode data
vdata, vtype = win32api.RegQueryValueEx(handle, local_vname)
if vdata or vdata in [0, '']:
ret['vtype'] = registry.vtype_reverse[vtype]
ret['vdata'] = vdata
if vtype == 7:
ret['vdata'] = [_to_mbcs(i) for i in vdata]
else:
ret['vdata'] = _to_mbcs(vdata)
else:
ret['comment'] = 'Empty Value'
except WindowsError: # pylint: disable=E0602
ret['vdata'] = ('(value not set)')
ret['vtype'] = 'REG_SZ'
except WindowsError as exc: # pylint: disable=E0602
except pywintypes.error as exc: # pylint: disable=E0602
log.debug(exc)
log.debug('Cannot find key: {0}\\{1}'.format(local_hive, local_key))
ret['comment'] = 'Cannot find key: {0}\\{1}'.format(local_hive, local_key)
@ -555,42 +489,47 @@ def set_value(hive,
salt '*' reg.set_value HKEY_LOCAL_MACHINE 'SOFTWARE\\Salt' 'version' '2015.5.2' \\
vtype=REG_LIST vdata='[a,b,c]'
'''
if PY2:
try:
local_hive = _mbcs_to_unicode(hive)
local_key = _mbcs_to_unicode(key)
local_vname = _mbcs_to_unicode(vname)
local_vtype = _mbcs_to_unicode(vtype)
local_vdata = _mbcs_to_unicode_wrap(vdata, local_vtype)
except TypeError as exc: # pylint: disable=E0602
log.error(exc, exc_info=True)
return False
else:
local_hive = hive
local_key = key
local_vname = vname
local_vdata = vdata
local_vtype = vtype
local_hive = _to_unicode(hive)
local_key = _to_unicode(key)
local_vname = _to_unicode(vname)
local_vtype = _to_unicode(vtype)
registry = Registry()
hkey = registry.hkeys[local_hive]
vtype_value = registry.vtype[local_vtype]
access_mask = registry.registry_32[use_32bit_registry] | _winreg.KEY_ALL_ACCESS
access_mask = registry.registry_32[use_32bit_registry] | win32con.KEY_ALL_ACCESS
# Check data type and cast to expected type
# int will automatically become long on 64bit numbers
# https://www.python.org/dev/peps/pep-0237/
# String Types to Unicode
if vtype_value in [1, 2]:
local_vdata = _to_unicode(vdata)
# Don't touch binary...
elif vtype_value == 3:
local_vdata = vdata
# Make sure REG_MULTI_SZ is a list of strings
elif vtype_value == 7:
local_vdata = [_to_unicode(i) for i in vdata]
# Everything else is int
else:
local_vdata = int(vdata)
if volatile:
create_options = registry.opttype['REG_OPTION_VOLATILE']
else:
create_options = registry.opttype['REG_OPTION_NON_VOLATILE']
try:
handle, _ = RegCreateKeyEx(hkey, local_key, access_mask,
handle, _ = win32api.RegCreateKeyEx(hkey, local_key, access_mask,
Options=create_options)
RegSetValueEx(handle, local_vname, 0, vtype_value, local_vdata)
RegFlushKey(handle)
RegCloseKey(handle)
win32api.RegSetValueEx(handle, local_vname, 0, vtype_value, local_vdata)
win32api.RegFlushKey(handle)
win32api.RegCloseKey(handle)
broadcast_change()
return True
except (win32apiError, SystemError, ValueError, TypeError) as exc: # pylint: disable=E0602
except (win32api.error, SystemError, ValueError, TypeError) as exc: # pylint: disable=E0602
log.error(exc, exc_info=True)
return False
@ -626,18 +565,14 @@ def delete_key_recursive(hive, key, use_32bit_registry=False):
salt '*' reg.delete_key_recursive HKLM SOFTWARE\\salt
'''
if PY2:
local_hive = _mbcs_to_unicode(hive)
local_key = _unicode_to_mbcs(key)
else:
local_hive = hive
local_key = key
local_hive = _to_unicode(hive)
local_key = _to_unicode(key)
# Instantiate the registry object
registry = Registry()
hkey = registry.hkeys[local_hive]
key_path = local_key
access_mask = registry.registry_32[use_32bit_registry] | _winreg.KEY_ALL_ACCESS
access_mask = registry.registry_32[use_32bit_registry] | win32con.KEY_ALL_ACCESS
if not _key_exists(local_hive, local_key, use_32bit_registry):
return False
@ -654,17 +589,17 @@ def delete_key_recursive(hive, key, use_32bit_registry=False):
i = 0
while True:
try:
subkey = _winreg.EnumKey(_key, i)
subkey = win32api.RegEnumKey(_key, i)
yield subkey
i += 1
except WindowsError: # pylint: disable=E0602
except pywintypes.error: # pylint: disable=E0602
break
def _traverse_registry_tree(_hkey, _keypath, _ret, _access_mask):
'''
Traverse the registry tree i.e. dive into the tree
'''
_key = _winreg.OpenKey(_hkey, _keypath, 0, _access_mask)
_key = win32api.RegOpenKeyEx(_hkey, _keypath, 0, _access_mask)
for subkeyname in _subkeys(_key):
subkeypath = r'{0}\{1}'.format(_keypath, subkeyname)
_ret = _traverse_registry_tree(_hkey, subkeypath, _ret, access_mask)
@ -683,8 +618,8 @@ def delete_key_recursive(hive, key, use_32bit_registry=False):
# Delete all sub_keys
for sub_key_path in key_list:
try:
key_handle = _winreg.OpenKey(hkey, sub_key_path, 0, access_mask)
_winreg.DeleteKey(key_handle, '')
key_handle = win32api.RegOpenKeyEx(hkey, sub_key_path, 0, access_mask)
win32api.RegDeleteKey(key_handle, '')
ret['Deleted'].append(r'{0}\{1}'.format(hive, sub_key_path))
except WindowsError as exc: # pylint: disable=E0602
log.error(exc, exc_info=True)
@ -723,23 +658,18 @@ def delete_value(hive, key, vname=None, use_32bit_registry=False):
salt '*' reg.delete_value HKEY_CURRENT_USER 'SOFTWARE\\Salt' 'version'
'''
if PY2:
local_hive = _mbcs_to_unicode(hive)
local_key = _unicode_to_mbcs(key)
local_vname = _unicode_to_mbcs(vname)
else:
local_hive = hive
local_key = key
local_vname = vname
local_hive = _to_unicode(hive)
local_key = _to_unicode(key)
local_vname = _to_unicode(vname)
registry = Registry()
hkey = registry.hkeys[local_hive]
access_mask = registry.registry_32[use_32bit_registry] | _winreg.KEY_ALL_ACCESS
access_mask = registry.registry_32[use_32bit_registry] | win32con.KEY_ALL_ACCESS
try:
handle = _winreg.OpenKey(hkey, local_key, 0, access_mask)
_winreg.DeleteValue(handle, local_vname)
_winreg.CloseKey(handle)
handle = win32api.RegOpenKeyEx(hkey, local_key, 0, access_mask)
win32api.RegDeleteValue(handle, local_vname)
win32api.RegCloseKey(handle)
broadcast_change()
return True
except WindowsError as exc: # pylint: disable=E0602

View file

@ -689,9 +689,24 @@ def _parse_settings_eth(opts, iface_type, enabled, iface):
if opt in opts:
result[opt] = opts[opt]
for opt in ['ipaddrs', 'ipv6addrs']:
if opt in opts:
result[opt] = opts[opt]
if 'ipaddrs' in opts:
result['ipaddrs'] = []
for opt in opts['ipaddrs']:
if salt.utils.validate.net.ipv4_addr(opt):
ip, prefix = [i.strip() for i in opt.split('/')]
result['ipaddrs'].append({'ipaddr': ip, 'prefix': prefix})
else:
msg = 'ipv4 CIDR is invalid'
log.error(msg)
raise AttributeError(msg)
if 'ipv6addrs' in opts:
for opt in opts['ipv6addrs']:
if not salt.utils.validate.net.ipv6_addr(opt):
msg = 'ipv6 CIDR is invalid'
log.error(msg)
raise AttributeError(msg)
result['ipv6addrs'] = opts['ipv6addrs']
if 'enable_ipv6' in opts:
result['enable_ipv6'] = opts['enable_ipv6']
@ -1055,8 +1070,8 @@ def build_routes(iface, **settings):
log.debug("IPv4 routes:\n{0}".format(opts4))
log.debug("IPv6 routes:\n{0}".format(opts6))
routecfg = template.render(routes=opts4)
routecfg6 = template.render(routes=opts6)
routecfg = template.render(routes=opts4, iface=iface)
routecfg6 = template.render(routes=opts6, iface=iface)
if settings['test']:
routes = _read_temp(routecfg)

View file

@ -493,6 +493,18 @@ def apply_(mods=None,
Values passed this way will override Pillar values set via
``pillar_roots`` or an external Pillar source.
exclude
Exclude specific states from execution. Accepts a list of sls names, a
comma-separated string of sls names, or a list of dictionaries
containing ``sls`` or ``id`` keys. Glob-patterns may be used to match
multiple states.
.. code-block:: bash
salt '*' state.apply exclude=bar,baz
salt '*' state.apply exclude=foo*
salt '*' state.apply exclude="[{'id': 'id_to_exclude'}, {'sls': 'sls_to_exclude'}]"
queue : False
Instead of failing immediately when another state run is in progress,
queue the new state run to begin running once the other has finished.
@ -755,6 +767,18 @@ def highstate(test=None, queue=False, **kwargs):
.. versionadded:: 2016.3.0
exclude
Exclude specific states from execution. Accepts a list of sls names, a
comma-separated string of sls names, or a list of dictionaries
containing ``sls`` or ``id`` keys. Glob-patterns may be used to match
multiple states.
.. code-block:: bash
salt '*' state.higstate exclude=bar,baz
salt '*' state.higstate exclude=foo*
salt '*' state.highstate exclude="[{'id': 'id_to_exclude'}, {'sls': 'sls_to_exclude'}]"
queue : False
Instead of failing immediately when another state run is in progress,
queue the new state run to begin running once the other has finished.
@ -905,6 +929,18 @@ def sls(mods, test=None, exclude=None, queue=False, **kwargs):
.. versionadded:: 2016.3.0
exclude
Exclude specific states from execution. Accepts a list of sls names, a
comma-separated string of sls names, or a list of dictionaries
containing ``sls`` or ``id`` keys. Glob-patterns may be used to match
multiple states.
.. code-block:: bash
salt '*' state.sls foo,bar,baz exclude=bar,baz
salt '*' state.sls foo,bar,baz exclude=ba*
salt '*' state.sls foo,bar,baz exclude="[{'id': 'id_to_exclude'}, {'sls': 'sls_to_exclude'}]"
queue : False
Instead of failing immediately when another state run is in progress,
queue the new state run to begin running once the other has finished.

View file

@ -67,7 +67,7 @@ from salt.modules.file import (check_hash, # pylint: disable=W0611
lstat, path_exists_glob, write, pardir, join, HASHES, HASHES_REVMAP,
comment, uncomment, _add_flags, comment_line, _regex_to_static,
_get_line_indent, apply_template_on_contents, dirname, basename,
list_backups_dir)
list_backups_dir, _assert_occurrence, _starts_till)
from salt.modules.file import normpath as normpath_
from salt.utils import namespaced_function as _namespaced_function
@ -98,7 +98,7 @@ def __virtual__():
global write, pardir, join, _add_flags, apply_template_on_contents
global path_exists_glob, comment, uncomment, _mkstemp_copy
global _regex_to_static, _get_line_indent, dirname, basename
global list_backups_dir, normpath_
global list_backups_dir, normpath_, _assert_occurrence, _starts_till
replace = _namespaced_function(replace, globals())
search = _namespaced_function(search, globals())
@ -165,6 +165,8 @@ def __virtual__():
basename = _namespaced_function(basename, globals())
list_backups_dir = _namespaced_function(list_backups_dir, globals())
normpath_ = _namespaced_function(normpath_, globals())
_assert_occurrence = _namespaced_function(_assert_occurrence, globals())
_starts_till = _namespaced_function(_starts_till, globals())
return __virtualname__
return (False, "Module win_file: module only works on Windows systems")

File diff suppressed because it is too large Load diff

View file

@ -69,33 +69,33 @@ def halt(timeout=5, in_seconds=False):
'''
Halt a running system.
:param int timeout:
Number of seconds before halting the system.
Default is 5 seconds.
Args:
:return: True is successful.
:rtype: bool
timeout (int):
Number of seconds before halting the system. Default is 5 seconds.
timeout
The wait time before the system will be shutdown.
in_seconds
Whether to treat timeout as seconds or minutes.
in_seconds (bool):
Whether to treat timeout as seconds or minutes.
.. versionadded:: 2015.8.0
Returns:
bool: ``True`` if successful, otherwise ``False``
CLI Example:
.. code-block:: bash
salt '*' system.halt 5
salt '*' system.halt 5 True
'''
return shutdown(timeout=timeout, in_seconds=in_seconds)
def init(runlevel): # pylint: disable=unused-argument
'''
Change the system runlevel on sysV compatible systems
Change the system runlevel on sysV compatible systems. Not applicable to
Windows
CLI Example:
@ -117,20 +117,19 @@ def poweroff(timeout=5, in_seconds=False):
'''
Power off a running system.
:param int timeout:
Number of seconds before powering off the system.
Default is 5 seconds.
Args:
:return: True if successful
:rtype: bool
timeout (int):
Number of seconds before powering off the system. Default is 5
seconds.
timeout
The wait time before the system will be shutdown.
in_seconds (bool):
Whether to treat timeout as seconds or minutes.
in_seconds
Whether to treat timeout as seconds or minutes.
.. versionadded:: 2015.8.0
.. versionadded:: 2015.8.0
Returns:
bool: ``True`` if successful, otherwise ``False``
CLI Example:
@ -146,35 +145,36 @@ def reboot(timeout=5, in_seconds=False, wait_for_reboot=False, # pylint: disabl
'''
Reboot a running system.
:param int timeout:
Number of minutes/seconds before rebooting the system. Minutes vs
seconds depends on the value of ``in_seconds``.
Default is 5 minutes.
Args:
:param bool in_seconds:
Whether to treat timeout as seconds or minutes.
timeout (int):
The number of minutes/seconds before rebooting the system. Use of
minutes or seconds depends on the value of ``in_seconds``. Default
is 5 minutes.
.. versionadded:: 2015.8.0
in_seconds (bool):
``True`` will cause the ``timeout`` parameter to be in seconds.
``False`` will be in minutes. Default is ``False``.
:param bool wait_for_reboot:
.. versionadded:: 2015.8.0
Sleeps for timeout + 30 seconds after reboot has been initiated.
This is useful for use in a highstate for example where
you have many states that could be ran after this one. Which you don't want
to start until after the restart i.e You could end up with a half finished state.
wait_for_reboot (bool)
``True`` will sleep for timeout + 30 seconds after reboot has been
initiated. This is useful for use in a highstate. For example, you
may have states that you want to apply only after the reboot.
Default is ``False``.
.. versionadded:: 2015.8.0
.. versionadded:: 2015.8.0
:param bool only_on_pending_reboot:
only_on_pending_reboot (bool):
If this is set to ``True``, then then the reboot will only proceed
if the system reports a pending reboot. Setting this parameter to
``True`` could be useful when calling this function from a final
housekeeping state intended to be executed at the end of a state run
(using *order: last*). Default is ``False``.
If this is set to True, then then the reboot will only proceed
if the system reports a pending reboot. Setting this parameter to
True could be useful when calling this function from a final housekeeping
state intended to be executed
at the end of a state run (using *order: last*).
:return: True if successful (a reboot will occur)
:rtype: bool
Returns:
bool: ``True`` if successful (a reboot will occur), otherwise ``False``
CLI Example:
@ -183,21 +183,16 @@ def reboot(timeout=5, in_seconds=False, wait_for_reboot=False, # pylint: disabl
salt '*' system.reboot 5
salt '*' system.reboot 5 True
As example of invoking this function from within a final housekeeping state
is as follows:
Example:
Invoking this function from a final housekeeping state:
.. code-block:: yaml
final housekeeping:
final_housekeeping:
module.run:
- name: system.reboot
- only_on_pending_reboot: True
- order: last
'''
ret = shutdown(timeout=timeout, reboot=True, in_seconds=in_seconds,
only_on_pending_reboot=only_on_pending_reboot)
@ -213,52 +208,59 @@ def shutdown(message=None, timeout=5, force_close=True, reboot=False, # pylint:
'''
Shutdown a running system.
:param str message:
A message to display to the user before shutting down.
Args:
:param int timeout:
The length of time that the shutdown dialog box should be displayed, in
seconds. While this dialog box is displayed, the shutdown can be stopped
by the shutdown_abort function.
message (str):
The message to display to the user before shutting down.
If timeout is not zero, InitiateSystemShutdown displays a dialog box on
the specified computer. The dialog box displays the name of the user
who called the function, displays the message specified by the
lpMessage parameter, and prompts the user to log off. The dialog box
beeps when it is created and remains on top of other windows in the
system. The dialog box can be moved but not closed. A timer counts down
the remaining time before a forced shutdown.
timeout (int):
The length of time (in seconds) that the shutdown dialog box should
be displayed. While this dialog box is displayed, the shutdown can
be aborted using the ``system.shutdown_abort`` function.
If timeout is zero, the computer shuts down without displaying the
dialog box, and the shutdown cannot be stopped by shutdown_abort.
If timeout is not zero, InitiateSystemShutdown displays a dialog box
on the specified computer. The dialog box displays the name of the
user who called the function, the message specified by the lpMessage
parameter, and prompts the user to log off. The dialog box beeps
when it is created and remains on top of other windows (system
modal). The dialog box can be moved but not closed. A timer counts
down the remaining time before the shutdown occurs.
Default is 5 minutes
If timeout is zero, the computer shuts down immediately without
displaying the dialog box and cannot be stopped by
``system.shutdown_abort``.
:param bool in_seconds:
Whether to treat timeout as seconds or minutes.
Default is 5 minutes
.. versionadded:: 2015.8.0
in_seconds (bool):
``True`` will cause the ``timeout`` parameter to be in seconds.
``False`` will be in minutes. Default is ``False``.
:param bool force_close:
True to force close all open applications. False displays a dialog box
instructing the user to close the applications.
.. versionadded:: 2015.8.0
:param bool reboot:
True restarts the computer immediately after shutdown.
False caches to disk and safely powers down the system.
force_close (bool):
``True`` will force close all open applications. ``False`` will
display a dialog box instructing the user to close open
applications. Default is ``True``.
:param bool only_on_pending_reboot:
If this is set to True, then then shutdown will only proceed
if the system reports a pending reboot.
reboot (bool):
``True`` restarts the computer immediately after shutdown. ``False``
powers down the system. Default is ``False``.
:return: True if successful (a shutdown or reboot will occur)
:rtype: bool
only_on_pending_reboot (bool):
If ``True`` the shutdown will only proceed if there is a reboot
pending. ``False`` will shutdown the system. Default is ``False``.
Returns:
bool:
``True`` if successful (a shutdown or reboot will occur), otherwise
``False``
CLI Example:
.. code-block:: bash
salt '*' system.shutdown 5
salt '*' system.shutdown "System will shutdown in 5 minutes"
'''
timeout = _convert_minutes_seconds(timeout, in_seconds)
@ -284,8 +286,8 @@ def shutdown_hard():
'''
Shutdown a running system with no timeout or warning.
:return: True if successful
:rtype: bool
Returns:
bool: ``True`` if successful, otherwise ``False``
CLI Example:
@ -301,8 +303,8 @@ def shutdown_abort():
Abort a shutdown. Only available while the dialog box is being
displayed to the user. Once the shutdown has initiated, it cannot be aborted
:return: True if successful
:rtype: bool
Returns:
bool: ``True`` if successful, otherwise ``False``
CLI Example:
@ -326,8 +328,8 @@ def lock():
'''
Lock the workstation.
:return: True if successful
:rtype: bool
Returns:
bool: ``True`` if successful, otherwise ``False``
CLI Example:
@ -342,12 +344,15 @@ def set_computer_name(name):
'''
Set the Windows computer name
:param str name:
The new name to give the computer. Requires a reboot to take effect.
Args:
:return:
Returns a dictionary containing the old and new names if successful.
False if not.
name (str):
The new name to give the computer. Requires a reboot to take effect.
Returns:
dict:
Returns a dictionary containing the old and new names if successful.
``False`` if not.
CLI Example:
@ -376,9 +381,10 @@ def get_pending_computer_name():
retrieving the pending computer name, ``False`` will be returned, and an
error message will be logged to the minion log.
:return:
Returns the pending name if pending restart. Returns none if not pending
restart.
Returns:
str:
Returns the pending name if pending restart. Returns ``None`` if not
pending restart.
CLI Example:
@ -400,8 +406,8 @@ def get_computer_name():
'''
Get the Windows computer name
:return:
Returns the computer name if found. Otherwise returns False
Returns:
str: Returns the computer name if found. Otherwise returns ``False``
CLI Example:
@ -417,10 +423,13 @@ def set_computer_desc(desc=None):
'''
Set the Windows computer description
:param str desc:
The computer description
Args:
:return: False if it fails. Description if successful.
desc (str):
The computer description
Returns:
str: Description if successful, otherwise ``False``
CLI Example:
@ -460,10 +469,9 @@ def get_system_info():
'''
Get system information.
:return:
Returns a Dictionary containing information about the system to include
Returns:
dict: Dictionary containing information about the system to include
name, description, version, etc...
:rtype: dict
CLI Example:
@ -514,8 +522,10 @@ def get_system_info():
def get_computer_desc():
'''
Get the Windows computer description
:return:
Returns the computer description if found. Otherwise returns False
Returns:
str: Returns the computer description if found. Otherwise returns
``False``
CLI Example:
@ -532,12 +542,12 @@ get_computer_description = salt.utils.alias_function(get_computer_desc, 'get_com
def get_hostname():
'''
.. versionadded:: 2016.3.0
Get the hostname of the windows minion
:return:
Returns the hostname of the windows minion
.. versionadded:: 2016.3.0
Returns:
str: Returns the hostname of the windows minion
CLI Example:
@ -552,13 +562,16 @@ def get_hostname():
def set_hostname(hostname):
'''
Set the hostname of the windows minion, requires a restart before this will
be updated.
.. versionadded:: 2016.3.0
Set the hostname of the windows minion, requires a restart before this
will be updated.
Args:
hostname (str): The hostname to set
:param str hostname:
The hostname to set
Returns:
bool: ``True`` if successful, otherwise ``False``
CLI Example:
@ -580,37 +593,41 @@ def join_domain(domain,
account_exists=False,
restart=False):
'''
Join a computer to an Active Directory domain. Requires reboot.
Join a computer to an Active Directory domain. Requires a reboot.
:param str domain:
The domain to which the computer should be joined, e.g.
``example.com``
Args:
:param str username:
Username of an account which is authorized to join computers to the
specified domain. Need to be either fully qualified like
``user@domain.tld`` or simply ``user``
domain (str):
The domain to which the computer should be joined, e.g.
``example.com``
:param str password:
Password of the specified user
username (str):
Username of an account which is authorized to join computers to the
specified domain. Needs to be either fully qualified like
``user@domain.tld`` or simply ``user``
:param str account_ou:
The DN of the OU below which the account for this computer should be
created when joining the domain, e.g.
``ou=computers,ou=departm_432,dc=my-company,dc=com``
password (str):
Password of the specified user
:param bool account_exists:
If set to ``True`` the computer will only join the domain if the account
already exists. If set to ``False`` the computer account will be created
if it does not exist, otherwise it will use the existing account.
Default is False
account_ou (str):
The DN of the OU below which the account for this computer should be
created when joining the domain, e.g.
``ou=computers,ou=departm_432,dc=my-company,dc=com``
:param bool restart: Restarts the computer after a successful join
account_exists (bool):
If set to ``True`` the computer will only join the domain if the
account already exists. If set to ``False`` the computer account
will be created if it does not exist, otherwise it will use the
existing account. Default is ``False``
.. versionadded:: 2015.8.2/2015.5.7
restart (bool):
``True`` will restart the computer after a successful join. Default
is ``False``
:returns: Returns a dictionary if successful. False if unsuccessful.
:rtype: dict, bool
.. versionadded:: 2015.8.2/2015.5.7
Returns:
dict: Returns a dictionary if successful, otherwise ``False``
CLI Example:
@ -678,34 +695,41 @@ def unjoin_domain(username=None,
disable=False,
restart=False):
r'''
Unjoin a computer from an Active Directory Domain. Requires restart.
Unjoin a computer from an Active Directory Domain. Requires a restart.
:param username:
Username of an account which is authorized to manage computer accounts
on the domain. Need to be fully qualified like ``user@domain.tld`` or
``domain.tld\user``. If domain not specified, the passed domain will be
used. If computer account doesn't need to be disabled, can be None.
Args:
:param str password:
Password of the specified user
username (str):
Username of an account which is authorized to manage computer
accounts on the domain. Needs to be a fully qualified name like
``user@domain.tld`` or ``domain.tld\user``. If the domain is not
specified, the passed domain will be used. If the computer account
doesn't need to be disabled after the computer is unjoined, this can
be ``None``.
:param str domain: The domain from which to unjoin the computer. Can be None
password (str):
The password of the specified user
:param str workgroup: The workgroup to join the computer to. Default is
``WORKGROUP``
domain (str):
The domain from which to unjoin the computer. Can be ``None``
.. versionadded:: 2015.8.2/2015.5.7
workgroup (str):
The workgroup to join the computer to. Default is ``WORKGROUP``
:param bool disable:
Disable the computer account in Active Directory. True to disable.
Default is False
.. versionadded:: 2015.8.2/2015.5.7
:param bool restart: Restart the computer after successful unjoin
disable (bool):
``True`` to disable the computer account in Active Directory.
Default is ``False``
.. versionadded:: 2015.8.2/2015.5.7
restart (bool):
``True`` will restart the computer after successful unjoin. Default
is ``False``
:returns: Returns a dictionary if successful. False if unsuccessful.
:rtype: dict, bool
.. versionadded:: 2015.8.2/2015.5.7
Returns:
dict: Returns a dictionary if successful, otherwise ``False``
CLI Example:
@ -772,8 +796,8 @@ def get_domain_workgroup():
.. versionadded:: 2015.5.7
.. versionadded:: 2015.8.2
:return: The name of the domain or workgroup
:rtype: str
Returns:
str: The name of the domain or workgroup
CLI Example:
@ -793,13 +817,16 @@ def get_domain_workgroup():
def _try_parse_datetime(time_str, fmts):
'''
Attempts to parse the input time_str as a date.
A helper function that attempts to parse the input time_str as a date.
:param str time_str: A string representing the time
:param list fmts: A list of date format strings
Args:
:return: Returns a datetime object if parsed properly. Otherwise None
:rtype datetime
time_str (str): A string representing the time
fmts (list): A list of date format strings
Returns:
datetime: Returns a datetime object if parsed properly, otherwise None
'''
result = None
for fmt in fmts:
@ -815,8 +842,8 @@ def get_system_time():
'''
Get the system time.
:return: Returns the system time in HH:MM:SS AM/PM format.
:rtype: str
Returns:
str: Returns the system time in HH:MM:SS AM/PM format.
CLI Example:
@ -841,15 +868,17 @@ def set_system_time(newtime):
'''
Set the system time.
:param str newtime:
The time to set. Can be any of the following formats.
- HH:MM:SS AM/PM
- HH:MM AM/PM
- HH:MM:SS (24 hour)
- HH:MM (24 hour)
Args:
:return: Returns True if successful. Otherwise False.
:rtype: bool
newtime (str):
The time to set. Can be any of the following formats:
- HH:MM:SS AM/PM
- HH:MM AM/PM
- HH:MM:SS (24 hour)
- HH:MM (24 hour)
Returns:
bool: ``True`` if successful, otherwise ``False``
CLI Example:
@ -881,15 +910,17 @@ def set_system_date_time(years=None,
element will be used. For example, if you don't pass the year, the current
system year will be used. (Used by set_system_date and set_system_time)
:param int years: Years digit, ie: 2015
:param int months: Months digit: 1 - 12
:param int days: Days digit: 1 - 31
:param int hours: Hours digit: 0 - 23
:param int minutes: Minutes digit: 0 - 59
:param int seconds: Seconds digit: 0 - 59
Args:
:return: True if successful. Otherwise False.
:rtype: bool
years (int): Years digit, ie: 2015
months (int): Months digit: 1 - 12
days (int): Days digit: 1 - 31
hours (int): Hours digit: 0 - 23
minutes (int): Minutes digit: 0 - 59
seconds (int): Seconds digit: 0 - 59
Returns:
bool: ``True`` if successful, otherwise ``False``
CLI Example:
@ -954,8 +985,8 @@ def get_system_date():
'''
Get the Windows system date
:return: Returns the system date.
:rtype: str
Returns:
str: Returns the system date
CLI Example:
@ -971,14 +1002,18 @@ def set_system_date(newdate):
'''
Set the Windows system date. Use <mm-dd-yy> format for the date.
:param str newdate:
The date to set. Can be any of the following formats
- YYYY-MM-DD
- MM-DD-YYYY
- MM-DD-YY
- MM/DD/YYYY
- MM/DD/YY
- YYYY/MM/DD
Args:
newdate (str):
The date to set. Can be any of the following formats
- YYYY-MM-DD
- MM-DD-YYYY
- MM-DD-YY
- MM/DD/YYYY
- MM/DD/YY
- YYYY/MM/DD
Returns:
bool: ``True`` if successful, otherwise ``False``
CLI Example:
@ -1003,8 +1038,8 @@ def start_time_service():
'''
Start the Windows time service
:return: True if successful. Otherwise False
:rtype: bool
Returns:
bool: ``True`` if successful, otherwise ``False``
CLI Example:
@ -1019,8 +1054,8 @@ def stop_time_service():
'''
Stop the Windows time service
:return: True if successful. Otherwise False
:rtype: bool
Returns:
bool: ``True`` if successful, otherwise ``False``
CLI Example:
@ -1033,13 +1068,15 @@ def stop_time_service():
def get_pending_component_servicing():
'''
Determine whether there are pending Component Based Servicing tasks that require a reboot.
:return: A boolean representing whether there are pending Component Based Servicing tasks.
:rtype: bool
Determine whether there are pending Component Based Servicing tasks that
require a reboot.
.. versionadded:: 2016.11.0
Returns:
bool: ``True`` if there are pending Component Based Servicing tasks,
otherwise ``False``
CLI Example:
.. code-block:: bash
@ -1062,13 +1099,15 @@ def get_pending_component_servicing():
def get_pending_domain_join():
'''
Determine whether there is a pending domain join action that requires a reboot.
:return: A boolean representing whether there is a pending domain join action.
:rtype: bool
Determine whether there is a pending domain join action that requires a
reboot.
.. versionadded:: 2016.11.0
Returns:
bool: ``True`` if there is a pending domain join action, otherwise
``False``
CLI Example:
.. code-block:: bash
@ -1103,13 +1142,15 @@ def get_pending_domain_join():
def get_pending_file_rename():
'''
Determine whether there are pending file rename operations that require a reboot.
:return: A boolean representing whether there are pending file rename operations.
:rtype: bool
Determine whether there are pending file rename operations that require a
reboot.
.. versionadded:: 2016.11.0
Returns:
bool: ``True`` if there are pending file rename operations, otherwise
``False``
CLI Example:
.. code-block:: bash
@ -1137,13 +1178,15 @@ def get_pending_file_rename():
def get_pending_servermanager():
'''
Determine whether there are pending Server Manager tasks that require a reboot.
:return: A boolean representing whether there are pending Server Manager tasks.
:rtype: bool
Determine whether there are pending Server Manager tasks that require a
reboot.
.. versionadded:: 2016.11.0
Returns:
bool: ``True`` if there are pending Server Manager tasks, otherwise
``False``
CLI Example:
.. code-block:: bash
@ -1176,11 +1219,11 @@ def get_pending_update():
'''
Determine whether there are pending updates that require a reboot.
:return: A boolean representing whether there are pending updates.
:rtype: bool
.. versionadded:: 2016.11.0
Returns:
bool: ``True`` if there are pending updates, otherwise ``False``
CLI Example:
.. code-block:: bash
@ -1209,28 +1252,24 @@ REBOOT_REQUIRED_NAME = 'Reboot required'
def set_reboot_required_witnessed():
r'''
.. versionadded:: 2016.11.0
This function is used to remember that
an event indicating that a reboot is required was witnessed.
This function relies on the salt-minion's ability to create the following
volatile registry key in the *HKLM* hive:
This function is used to remember that an event indicating that a reboot is
required was witnessed. This function relies on the salt-minion's ability to
create the following volatile registry key in the *HKLM* hive:
*SYSTEM\\CurrentControlSet\\Services\\salt-minion\\Volatile-Data*
Because this registry key is volatile, it will not persist
beyond the current boot session.
Also, in the scope of this key, the name *'Reboot required'* will be
assigned the value of *1*.
Because this registry key is volatile, it will not persist beyond the
current boot session. Also, in the scope of this key, the name *'Reboot
required'* will be assigned the value of *1*.
(For the time being, this this function is being used
whenever an install completes with exit code 3010 and
this usage can be extended where appropriate in the future.)
For the time being, this function is being used whenever an install
completes with exit code 3010 and can be extended where appropriate in the
future.
:return: A boolean indicating whether or not the salt minion was
able to perform the necessary registry operations.
.. versionadded:: 2016.11.0
:rtype: bool
Returns:
bool: ``True`` if successful, otherwise ``False``
CLI Example:
@ -1249,20 +1288,18 @@ def set_reboot_required_witnessed():
def get_reboot_required_witnessed():
'''
Determine if at any time during the current boot session the salt minion
witnessed an event indicating that a reboot is required.
This function will return ``True`` if an install completed with exit
code 3010 during the current boot session and can be extended where
appropriate in the future.
.. versionadded:: 2016.11.0
This tells us if, at any time during the current boot session
the salt minion witnessed an event indicating
that a reboot is required.
(For the time being, this function will return True
if an install completed with exit code 3010 during the current
boot session and this usage can be extended where appropriate
in the future)
:return: a boolean which will be True if the salt-minion reported
a required reboot during the current boot session, otherwise False.
:rtype: bool
Returns:
bool: ``True`` if the ``Requires reboot`` registry flag is set to ``1``,
otherwise ``False``
CLI Example:
@ -1281,11 +1318,11 @@ def get_pending_reboot():
'''
Determine whether there is a reboot pending.
:return: A boolean representing whether reboots are pending.
:rtype: bool
.. versionadded:: 2016.11.0
Returns:
bool: ``True`` if the system is pending reboot, otherwise ``False``
CLI Example:
.. code-block:: bash

View file

@ -106,6 +106,13 @@ A REST API for Salt
expire_responses : True
Whether to check for and kill HTTP responses that have exceeded the
default timeout.
.. deprecated:: 2016.11.9, 2017.7.3, Oxygen
The "expire_responses" configuration setting, which corresponds
to the ``timeout_monitor`` setting in CherryPy, is no longer
supported in CherryPy versions >= 12.0.0.
max_request_body_size : ``1048576``
Maximum size for the HTTP request body.
collect_stats : False
@ -490,6 +497,8 @@ import salt.ext.six as six
# Import Salt libs
import salt
import salt.auth
import salt.exceptions
import salt.utils
import salt.utils.event
# Import salt-api libs
@ -734,7 +743,8 @@ def hypermedia_handler(*args, **kwargs):
except (salt.exceptions.SaltDaemonNotRunning,
salt.exceptions.SaltReqTimeoutError) as exc:
raise cherrypy.HTTPError(503, exc.strerror)
except (cherrypy.TimeoutError, salt.exceptions.SaltClientTimeout):
except (cherrypy.TimeoutError if hasattr(cherrypy, 'TimeoutError') else None,
salt.exceptions.SaltClientTimeout):
raise cherrypy.HTTPError(504)
except cherrypy.CherryPyException:
raise
@ -2548,8 +2558,6 @@ class API(object):
'server.socket_port': self.apiopts.get('port', 8000),
'server.thread_pool': self.apiopts.get('thread_pool', 100),
'server.socket_queue_size': self.apiopts.get('queue_size', 30),
'engine.timeout_monitor.on': self.apiopts.get(
'expire_responses', True),
'max_request_body_size': self.apiopts.get(
'max_request_body_size', 1048576),
'debug': self.apiopts.get('debug', False),
@ -2567,6 +2575,14 @@ class API(object):
},
}
if salt.utils.version_cmp(cherrypy.__version__, '12.0.0') < 0:
# CherryPy >= 12.0 no longer supports "timeout_monitor", only set
# this config option when using an older version of CherryPy.
# See Issue #44601 for more information.
conf['global']['engine.timeout_monitor.on'] = self.apiopts.get(
'expire_responses', True
)
if cpstats and self.apiopts.get('collect_stats', False):
conf['/']['tools.cpstats.on'] = True

View file

@ -142,6 +142,17 @@ def get_printout(out, opts=None, **kwargs):
# See Issue #29796 for more information.
out = opts['output']
# Handle setting the output when --static is passed.
if not out and opts.get('static'):
if opts.get('output'):
out = opts['output']
elif opts.get('fun', '').split('.')[0] == 'state':
# --static doesn't have an output set at this point, but if we're
# running a state function and "out" hasn't already been set, we
# should set the out variable to "highstate". Otherwise state runs
# are set to "nested" below. See Issue #44556 for more information.
out = 'highstate'
if out == 'text':
out = 'txt'
elif out is None or out == '':

View file

@ -254,14 +254,14 @@ def returner(ret):
with _get_serv(ret, commit=True) as cur:
sql = '''INSERT INTO salt_returns
(fun, jid, return, id, success, full_ret, alter_time)
VALUES (%s, %s, %s, %s, %s, %s, %s)'''
VALUES (%s, %s, %s, %s, %s, %s, to_timestamp(%s))'''
cur.execute(sql, (ret['fun'], ret['jid'],
psycopg2.extras.Json(ret['return']),
ret['id'],
ret.get('success', False),
psycopg2.extras.Json(ret),
time.strftime('%Y-%m-%d %H:%M:%S %z', time.localtime())))
time.time()))
except salt.exceptions.SaltMasterError:
log.critical('Could not store return with pgjsonb returner. PostgreSQL server unavailable.')
@ -278,9 +278,9 @@ def event_return(events):
tag = event.get('tag', '')
data = event.get('data', '')
sql = '''INSERT INTO salt_events (tag, data, master_id, alter_time)
VALUES (%s, %s, %s, %s)'''
VALUES (%s, %s, %s, to_timestamp(%s))'''
cur.execute(sql, (tag, psycopg2.extras.Json(data),
__opts__['id'], time.strftime('%Y-%m-%d %H:%M:%S %z', time.localtime())))
__opts__['id'], time.time()))
def save_load(jid, load, minions=None):

View file

@ -662,7 +662,7 @@ class State(object):
.format(', '.join(VALID_PILLAR_ENC))
)
self._pillar_enc = pillar_enc
if initial_pillar is not None:
if initial_pillar:
self.opts['pillar'] = initial_pillar
if self._pillar_override:
self.opts['pillar'] = salt.utils.dictupdate.merge(

View file

@ -299,7 +299,7 @@ def present(name,
identifier
Custom-defined identifier for tracking the cron line for future crontab
edits. This defaults to the state id
edits. This defaults to the state name
special
A special keyword to specify periodicity (eg. @reboot, @hourly...).
@ -386,7 +386,7 @@ def absent(name,
identifier
Custom-defined identifier for tracking the cron line for future crontab
edits. This defaults to the state id
edits. This defaults to the state name
special
The special keyword used in the job (eg. @reboot, @hourly...).

View file

@ -1605,6 +1605,9 @@ def managed(name,
'name': name,
'result': True}
if not name:
return _error(ret, 'Destination file name is required')
if mode is not None and salt.utils.is_windows():
return _error(ret, 'The \'mode\' option is not supported on Windows')
@ -1755,8 +1758,6 @@ def managed(name,
ret['comment'] = 'Error while applying template on contents'
return ret
if not name:
return _error(ret, 'Must provide name to file.exists')
user = _test_owner(kwargs, user=user)
if salt.utils.is_windows():
if group is not None:

View file

@ -696,7 +696,7 @@ def edited_conf(name, lxc_conf=None, lxc_conf_unset=None):
# to keep this function around and cannot officially remove it. Progress of
# the new function will be tracked in https://github.com/saltstack/salt/issues/35523
salt.utils.warn_until(
'Oxygen',
'Fluorine',
'This state is unsuitable for setting parameters that appear more '
'than once in an LXC config file, or parameters which must appear in '
'a certain order (such as when configuring more than one network '

View file

@ -59,6 +59,7 @@ from __future__ import absolute_import
# Import python libs
import logging
import salt.utils
log = logging.getLogger(__name__)
@ -186,13 +187,14 @@ def present(name,
use_32bit_registry=use_32bit_registry)
if vdata == reg_current['vdata'] and reg_current['success']:
ret['comment'] = '{0} in {1} is already configured'.\
format(vname if vname else '(Default)', name)
ret['comment'] = u'{0} in {1} is already configured' \
''.format(salt.utils.to_unicode(vname, 'utf-8') if vname else u'(Default)',
salt.utils.to_unicode(name, 'utf-8'))
return ret
add_change = {'Key': r'{0}\{1}'.format(hive, key),
'Entry': '{0}'.format(vname if vname else '(Default)'),
'Value': '{0}'.format(vdata)}
'Entry': u'{0}'.format(salt.utils.to_unicode(vname, 'utf-8') if vname else u'(Default)'),
'Value': salt.utils.to_unicode(vdata, 'utf-8')}
# Check for test option
if __opts__['test']:

View file

@ -65,7 +65,8 @@ def exists(name, index=None):
'''
Add the directory to the system PATH at index location
index: where the directory should be placed in the PATH (default: None)
index: where the directory should be placed in the PATH (default: None).
This is 0-indexed, so 0 means to prepend at the very start of the PATH.
[Note: Providing no index will append directory to PATH and
will not enforce its location within the PATH.]
@ -96,7 +97,7 @@ def exists(name, index=None):
try:
currIndex = sysPath.index(path)
if index:
if index is not None:
index = int(index)
if index < 0:
index = len(sysPath) + index + 1
@ -115,7 +116,7 @@ def exists(name, index=None):
except ValueError:
pass
if not index:
if index is None:
index = len(sysPath) # put it at the end
ret['changes']['added'] = '{0} will be added at index {1}'.format(name, index)
if __opts__['test']:

View file

@ -5,5 +5,6 @@
/{{route.netmask}}
{%- endif -%}
{%- if route.gateway %} via {{route.gateway}}
{%- else %} dev {{iface}}
{%- endif %}
{% endfor -%}

View file

@ -19,12 +19,17 @@ DEVICE="{{name}}"
{%endif%}{% if ipaddr_end %}IPADDR_END="{{ipaddr_end}}"
{%endif%}{% if netmask %}NETMASK="{{netmask}}"
{%endif%}{% if prefix %}PREFIX="{{prefix}}"
{%endif%}{% if ipaddrs %}{% for i in ipaddrs -%}
IPADDR{{loop.index}}="{{i['ipaddr']}}"
PREFIX{{loop.index}}="{{i['prefix']}}"
{% endfor -%}
{%endif%}{% if gateway %}GATEWAY="{{gateway}}"
{%endif%}{% if enable_ipv6 %}IPV6INIT="yes"
{% if ipv6_autoconf %}IPV6_AUTOCONF="{{ipv6_autoconf}}"
{%endif%}{% if dhcpv6c %}DHCPV6C="{{dhcpv6c}}"
{%endif%}{% if ipv6addr %}IPV6ADDR="{{ipv6addr}}"
{%endif%}{% if ipv6gateway %}IPV6_DEFAULTGW="{{ipv6gateway}}"
{%endif%}{% if ipv6addrs %}IPV6ADDR_SECONDARIES="{{ ipv6addrs|join(' ') }}"
{%endif%}{% if ipv6_peerdns %}IPV6_PEERDNS="{{ipv6_peerdns}}"
{%endif%}{% if ipv6_defroute %}IPV6_DEFROUTE="{{ipv6_defroute}}"
{%endif%}{% if ipv6_peerroutes %}IPV6_PEERROUTES="{{ipv6_peerroutes}}"

View file

@ -1115,10 +1115,10 @@ def format_call(fun,
continue
extra[key] = copy.deepcopy(value)
# We'll be showing errors to the users until Salt Oxygen comes out, after
# We'll be showing errors to the users until Salt Fluorine comes out, after
# which, errors will be raised instead.
warn_until(
'Oxygen',
'Fluorine',
'It\'s time to start raising `SaltInvocationError` instead of '
'returning warnings',
# Let's not show the deprecation warning on the console, there's no
@ -1155,7 +1155,7 @@ def format_call(fun,
'{0}. If you were trying to pass additional data to be used '
'in a template context, please populate \'context\' with '
'\'key: value\' pairs. Your approach will work until Salt '
'Oxygen is out.{1}'.format(
'Fluorine is out.{1}'.format(
msg,
'' if 'full' not in ret else ' Please update your state files.'
)
@ -3070,6 +3070,8 @@ def to_unicode(s, encoding=None):
'''
Given str or unicode, return unicode (str for python 3)
'''
if s is None:
return s
if six.PY3:
return to_str(s, encoding)
else:

View file

@ -120,6 +120,8 @@ class _AtomicWFile(object):
self._fh.close()
if os.path.isfile(self._filename):
shutil.copymode(self._filename, self._tmp_filename)
st = os.stat(self._filename)
os.chown(self._tmp_filename, st.st_uid, st.st_gid)
atomic_rename(self._tmp_filename, self._filename)
def __exit__(self, exc_type, exc_value, traceback):

View file

@ -26,6 +26,16 @@ REMOTE_PROTOS = ('http', 'https', 'ftp', 'swift', 's3')
VALID_PROTOS = ('salt', 'file') + REMOTE_PROTOS
def __clean_tmp(tmp):
'''
Remove temporary files
'''
try:
salt.utils.rm_rf(tmp)
except Exception:
pass
def guess_archive_type(name):
'''
Guess an archive type (tar, zip, or rar) by its file extension
@ -93,7 +103,15 @@ def copyfile(source, dest, backup_mode='', cachedir=''):
fstat = os.stat(dest)
except OSError:
pass
shutil.move(tgt, dest)
# The move could fail if the dest has xattr protections, so delete the
# temp file in this case
try:
shutil.move(tgt, dest)
except Exception:
__clean_tmp(tgt)
raise
if fstat is not None:
os.chown(dest, fstat.st_uid, fstat.st_gid)
os.chmod(dest, fstat.st_mode)
@ -111,10 +129,7 @@ def copyfile(source, dest, backup_mode='', cachedir=''):
subprocess.call(cmd, stdout=dev_null, stderr=dev_null)
if os.path.isfile(tgt):
# The temp file failed to move
try:
os.remove(tgt)
except Exception:
pass
__clean_tmp(tgt)
def rename(src, dst):

View file

@ -3,12 +3,23 @@
# Import python libs
from __future__ import absolute_import
import getpass
import grp
import pwd
import os
import shutil
import sys
# Posix only
try:
import grp
import pwd
except ImportError:
pass
# Windows only
try:
import win32file
except ImportError:
pass
# Import Salt Testing libs
from salttesting import skipIf
from salttesting.helpers import ensure_in_syspath
@ -23,6 +34,16 @@ import salt.utils
from salt.modules import file as filemod
def symlink(source, link_name):
'''
Handle symlinks on Windows with Python < 3.2
'''
if salt.utils.is_windows():
win32file.CreateSymbolicLink(link_name, source)
else:
os.symlink(source, link_name)
class FileModuleTest(integration.ModuleCase):
'''
Validate the file module
@ -30,27 +51,27 @@ class FileModuleTest(integration.ModuleCase):
def setUp(self):
self.myfile = os.path.join(integration.TMP, 'myfile')
with salt.utils.fopen(self.myfile, 'w+') as fp:
fp.write('Hello\n')
fp.write('Hello' + os.linesep)
self.mydir = os.path.join(integration.TMP, 'mydir/isawesome')
if not os.path.isdir(self.mydir):
# left behind... Don't fail because of this!
os.makedirs(self.mydir)
self.mysymlink = os.path.join(integration.TMP, 'mysymlink')
if os.path.islink(self.mysymlink):
if os.path.islink(self.mysymlink) or os.path.isfile(self.mysymlink):
os.remove(self.mysymlink)
os.symlink(self.myfile, self.mysymlink)
symlink(self.myfile, self.mysymlink)
self.mybadsymlink = os.path.join(integration.TMP, 'mybadsymlink')
if os.path.islink(self.mybadsymlink):
if os.path.islink(self.mybadsymlink) or os.path.isfile(self.mybadsymlink):
os.remove(self.mybadsymlink)
os.symlink('/nonexistentpath', self.mybadsymlink)
symlink('/nonexistentpath', self.mybadsymlink)
super(FileModuleTest, self).setUp()
def tearDown(self):
if os.path.isfile(self.myfile):
os.remove(self.myfile)
if os.path.islink(self.mysymlink):
if os.path.islink(self.mysymlink) or os.path.isfile(self.mysymlink):
os.remove(self.mysymlink)
if os.path.islink(self.mybadsymlink):
if os.path.islink(self.mybadsymlink) or os.path.isfile(self.mybadsymlink):
os.remove(self.mybadsymlink)
shutil.rmtree(self.mydir, ignore_errors=True)
super(FileModuleTest, self).tearDown()
@ -293,6 +314,23 @@ class FileModuleTest(integration.ModuleCase):
else:
self.assertItemsEqual(ret, ['file://' + self.myfile, 'filehash'])
def test_file_line_changes_format(self):
'''
Test file.line changes output formatting.
Issue #41474
'''
ret = self.minion_run('file.line', self.myfile, 'Goodbye',
mode='insert', after='Hello')
self.assertIn('Hello' + os.linesep + '+Goodbye', ret)
def test_file_line_content(self):
self.minion_run('file.line', self.myfile, 'Goodbye',
mode='insert', after='Hello')
with salt.utils.fopen(self.myfile, 'r') as fp:
content = fp.read()
self.assertEqual(content, 'Hello' + os.linesep + 'Goodbye' + os.linesep)
if __name__ == '__main__':
from integration import run_tests
run_tests(FileModuleTest)

View file

@ -106,6 +106,62 @@ class OutputReturnTest(integration.ShellCase):
trace = traceback.format_exc()
self.assertEqual(trace, '')
def test_output_highstate(self):
'''
Regression tests for the highstate outputter. Calls a basic state with various
flags. Each comparison should be identical when successful.
'''
# Test basic highstate output. No frills.
expected = ['minion:', ' ID: simple-ping', ' Function: module.run',
' Name: test.ping', ' Result: True',
' Comment: Module function test.ping executed',
' Changes: ', ' ret:', ' True',
'Summary for minion', 'Succeeded: 1 (changed=1)', 'Failed: 0',
'Total states run: 1']
state_run = self.run_salt('"minion" state.sls simple-ping')
for expected_item in expected:
self.assertIn(expected_item, state_run)
# Test highstate output while also passing --out=highstate.
# This is a regression test for Issue #29796
state_run = self.run_salt('"minion" state.sls simple-ping --out=highstate')
for expected_item in expected:
self.assertIn(expected_item, state_run)
# Test highstate output when passing --static and running a state function.
# See Issue #44556.
state_run = self.run_salt('"minion" state.sls simple-ping --static')
for expected_item in expected:
self.assertIn(expected_item, state_run)
# Test highstate output when passing --static and --out=highstate.
# See Issue #44556.
state_run = self.run_salt('"minion" state.sls simple-ping --static --out=highstate')
for expected_item in expected:
self.assertIn(expected_item, state_run)
def test_output_highstate_falls_back_nested(self):
'''
Tests outputter when passing --out=highstate with a non-state call. This should
fall back to "nested" output.
'''
expected = ['minion:', ' True']
ret = self.run_salt('"minion" test.ping --out=highstate')
self.assertEqual(ret, expected)
def test_static_simple(self):
'''
Tests passing the --static option with a basic test.ping command. This
should be the "nested" output.
'''
expected = ['minion:', ' True']
ret = self.run_salt('"minion" test.ping --static')
self.assertEqual(ret, expected)
if __name__ == '__main__':
from integration import run_tests

View file

@ -104,9 +104,9 @@ def _test_managed_file_mode_keep_helper(testcase, local=False):
# Get the current mode so that we can put the file back the way we
# found it when we're done.
grail_fs_mode = os.stat(grail_fs_path).st_mode
initial_mode = 504 # 0770 octal
new_mode_1 = 384 # 0600 octal
new_mode_2 = 420 # 0644 octal
initial_mode = 0o770
new_mode_1 = 0o600
new_mode_2 = 0o644
# Set the initial mode, so we can be assured that when we set the mode
# to "keep", we're actually changing the permissions of the file to the
@ -587,6 +587,86 @@ class FileTest(integration.ModuleCase, integration.SaltReturnAssertsMixIn):
for typ in managed_files:
os.remove(managed_files[typ])
def test_managed_local_source_with_source_hash(self):
'''
Make sure that we enforce the source_hash even with local files
'''
name = os.path.join(integration.TMP, 'local_source_with_source_hash')
local_path = os.path.join(
integration.FILES, 'file', 'base', 'grail', 'scene33')
actual_hash = '567fd840bf1548edc35c48eb66cdd78bfdfcccff'
# Reverse the actual hash
bad_hash = actual_hash[::-1]
def remove_file():
try:
os.remove(name)
except OSError as exc:
if exc.errno != errno.ENOENT:
raise
def do_test(clean=False):
for proto in ('file://', ''):
source = proto + local_path
log.debug('Trying source %s', source)
try:
ret = self.run_state(
'file.managed',
name=name,
source=source,
source_hash='sha1={0}'.format(bad_hash))
self.assertSaltFalseReturn(ret)
ret = ret[next(iter(ret))]
# Shouldn't be any changes
self.assertFalse(ret['changes'])
# Check that we identified a hash mismatch
self.assertIn(
'does not match actual checksum', ret['comment'])
ret = self.run_state(
'file.managed',
name=name,
source=source,
source_hash='sha1={0}'.format(actual_hash))
self.assertSaltTrueReturn(ret)
finally:
if clean:
remove_file()
remove_file()
log.debug('Trying with nonexistant destination file')
do_test()
log.debug('Trying with destination file already present')
with salt.utils.fopen(name, 'w'):
pass
try:
do_test(clean=False)
finally:
remove_file()
def test_managed_local_source_does_not_exist(self):
'''
Make sure that we exit gracefully when a local source doesn't exist
'''
name = os.path.join(integration.TMP, 'local_source_does_not_exist')
local_path = os.path.join(
integration.FILES, 'file', 'base', 'grail', 'scene99')
for proto in ('file://', ''):
source = proto + local_path
log.debug('Trying source %s', source)
ret = self.run_state(
'file.managed',
name=name,
source=source)
self.assertSaltFalseReturn(ret)
ret = ret[next(iter(ret))]
# Shouldn't be any changes
self.assertFalse(ret['changes'])
# Check that we identified a hash mismatch
self.assertIn(
'does not exist', ret['comment'])
def test_directory(self):
'''
file.directory

View file

@ -31,6 +31,14 @@ from salt.grains import core
core.__salt__ = {}
core.__opts__ = {}
log = logging.getLogger(__name__)
IPv4Address = salt.ext.ipaddress.IPv4Address
IPv6Address = salt.ext.ipaddress.IPv6Address
IP4_LOCAL = '127.0.0.1'
IP4_ADD1 = '10.0.0.1'
IP4_ADD2 = '10.0.0.2'
IP6_LOCAL = '::1'
IP6_ADD1 = '2001:4860:4860::8844'
IP6_ADD2 = '2001:4860:4860::8888'
@skipIf(NO_MOCK, NO_MOCK_REASON)
@ -483,6 +491,130 @@ PATCHLEVEL = 3
'Docker'
)
def _check_ipaddress(self, value, ip_v):
'''
check if ip address in a list is valid
'''
for val in value:
assert isinstance(val, six.string_types)
ip_method = 'is_ipv{0}'.format(ip_v)
self.assertTrue(getattr(salt.utils.network, ip_method)(val))
def _check_empty(self, key, value, empty):
'''
if empty is False and value does not exist assert error
if empty is True and value exists assert error
'''
if not empty and not value:
raise Exception("{0} is empty, expecting a value".format(key))
elif empty and value:
raise Exception("{0} is suppose to be empty. value: {1} \
exists".format(key, value))
@skipIf(not salt.utils.is_linux(), 'System is not Linux')
def test_fqdn_return(self):
'''
test ip4 and ip6 return values
'''
net_ip4_mock = [IP4_LOCAL, IP4_ADD1, IP4_ADD2]
net_ip6_mock = [IP6_LOCAL, IP6_ADD1, IP6_ADD2]
self._run_fqdn_tests(net_ip4_mock, net_ip6_mock,
ip4_empty=False, ip6_empty=False)
@skipIf(not salt.utils.is_linux(), 'System is not Linux')
def test_fqdn6_empty(self):
'''
test when ip6 is empty
'''
net_ip4_mock = [IP4_LOCAL, IP4_ADD1, IP4_ADD2]
net_ip6_mock = []
self._run_fqdn_tests(net_ip4_mock, net_ip6_mock,
ip4_empty=False)
@skipIf(not salt.utils.is_linux(), 'System is not Linux')
def test_fqdn4_empty(self):
'''
test when ip4 is empty
'''
net_ip4_mock = []
net_ip6_mock = [IP6_LOCAL, IP6_ADD1, IP6_ADD2]
self._run_fqdn_tests(net_ip4_mock, net_ip6_mock,
ip6_empty=False)
@skipIf(not salt.utils.is_linux(), 'System is not Linux')
def test_fqdn_all_empty(self):
'''
test when both ip4 and ip6 are empty
'''
net_ip4_mock = []
net_ip6_mock = []
self._run_fqdn_tests(net_ip4_mock, net_ip6_mock)
def _run_fqdn_tests(self, net_ip4_mock, net_ip6_mock,
ip6_empty=True, ip4_empty=True):
def _check_type(key, value, ip4_empty, ip6_empty):
'''
check type and other checks
'''
assert isinstance(value, list)
if '4' in key:
self._check_empty(key, value, ip4_empty)
self._check_ipaddress(value, ip_v='4')
elif '6' in key:
self._check_empty(key, value, ip6_empty)
self._check_ipaddress(value, ip_v='6')
ip4_mock = [(2, 1, 6, '', (IP4_ADD1, 0)),
(2, 3, 0, '', (IP4_ADD2, 0))]
ip6_mock = [(10, 1, 6, '', (IP6_ADD1, 0, 0, 0)),
(10, 3, 0, '', (IP6_ADD2, 0, 0, 0))]
with patch.dict(core.__opts__, {'ipv6': False}):
with patch.object(salt.utils.network, 'ip_addrs',
MagicMock(return_value=net_ip4_mock)):
with patch.object(salt.utils.network, 'ip_addrs6',
MagicMock(return_value=net_ip6_mock)):
with patch.object(core.socket, 'getaddrinfo', side_effect=[ip4_mock, ip6_mock]):
get_fqdn = core.ip_fqdn()
ret_keys = ['fqdn_ip4', 'fqdn_ip6', 'ipv4', 'ipv6']
for key in ret_keys:
value = get_fqdn[key]
_check_type(key, value, ip4_empty, ip6_empty)
@skipIf(not salt.utils.is_linux(), 'System is not Linux')
def test_dns_return(self):
'''
test the return for a dns grain. test for issue:
https://github.com/saltstack/salt/issues/41230
'''
resolv_mock = {'domain': '', 'sortlist': [], 'nameservers':
[IPv4Address(IP4_ADD1),
IPv6Address(IP6_ADD1)], 'ip4_nameservers':
[IPv4Address(IP4_ADD1)],
'search': ['test.saltstack.com'], 'ip6_nameservers':
[IPv6Address(IP6_ADD1)], 'options': []}
ret = {'dns': {'domain': '', 'sortlist': [], 'nameservers':
[IP4_ADD1, IP6_ADD1], 'ip4_nameservers':
[IP4_ADD1], 'search': ['test.saltstack.com'],
'ip6_nameservers': [IP6_ADD1], 'options':
[]}}
self._run_dns_test(resolv_mock, ret)
def _run_dns_test(self, resolv_mock, ret):
with patch.object(salt.utils, 'is_windows',
MagicMock(return_value=False)):
with patch.dict(core.__opts__, {'ipv6': False}):
with patch.object(salt.utils.dns, 'parse_resolv',
MagicMock(return_value=resolv_mock)):
get_dns = core.dns()
self.assertEqual(get_dns, ret)
if __name__ == '__main__':
from integration import run_tests

View file

@ -14,6 +14,7 @@ from salttesting.mock import (
patch)
from salttesting.helpers import ensure_in_syspath
from salt.ext.six.moves import range
ensure_in_syspath('../../')
@ -62,7 +63,6 @@ class RhipTestCase(TestCase):
'''
with patch.dict(rh_ip.__grains__, {'os': 'Fedora'}):
with patch.object(rh_ip, '_raise_error_iface', return_value=None):
self.assertRaises(AttributeError,
rh_ip.build_interface,
'iface', 'slave', True)
@ -72,34 +72,53 @@ class RhipTestCase(TestCase):
rh_ip.build_interface,
'iface', 'eth', True, netmask='255.255.255.255', prefix=32,
test=True)
self.assertRaises(AttributeError,
rh_ip.build_interface,
'iface', 'eth', True, ipaddrs=['A'],
test=True)
self.assertRaises(AttributeError,
rh_ip.build_interface,
'iface', 'eth', True, ipv6addrs=['A'],
test=True)
with patch.object(rh_ip, '_parse_settings_bond', MagicMock()):
mock = jinja2.exceptions.TemplateNotFound('foo')
with patch.object(jinja2.Environment,
'get_template',
MagicMock(side_effect=mock)):
self.assertEqual(rh_ip.build_interface('iface',
'vlan',
True), '')
with patch.object(rh_ip, '_read_temp', return_value='A'):
for osrelease in range(5, 8):
with patch.dict(rh_ip.__grains__, {'os': 'RedHat', 'osrelease': str(osrelease)}):
with patch.object(rh_ip, '_raise_error_iface', return_value=None):
with patch.object(rh_ip, '_parse_settings_bond', MagicMock()):
mock = jinja2.exceptions.TemplateNotFound('foo')
with patch.object(jinja2.Environment,
'get_template', MagicMock()):
'get_template',
MagicMock(side_effect=mock)):
self.assertEqual(rh_ip.build_interface('iface',
'vlan',
True,
test='A'),
'A')
True), '')
with patch.object(rh_ip, '_write_file_iface',
return_value=None):
with patch.object(os.path, 'join',
return_value='A'):
with patch.object(rh_ip, '_read_file',
with patch.object(rh_ip, '_read_temp', return_value='A'):
with patch.object(jinja2.Environment,
'get_template', MagicMock()):
self.assertEqual(rh_ip.build_interface('iface',
'vlan',
True,
test='A'),
'A')
with patch.object(rh_ip, '_write_file_iface',
return_value=None):
with patch.object(os.path, 'join',
return_value='A'):
self.assertEqual(rh_ip.build_interface
('iface', 'vlan',
True), 'A')
with patch.object(rh_ip, '_read_file',
return_value='A'):
self.assertEqual(rh_ip.build_interface
('iface', 'vlan',
True), 'A')
if osrelease > 6:
with patch.dict(rh_ip.__salt__, {'network.interfaces': lambda: {'eth': True}}):
self.assertEqual(rh_ip.build_interface
('iface', 'eth', True,
ipaddrs=['127.0.0.1/8']), 'A')
self.assertEqual(rh_ip.build_interface
('iface', 'eth', True,
ipv6addrs=['fc00::1/128']), 'A')
def test_build_routes(self):
'''

View file

@ -581,7 +581,7 @@ class FileTestCase(TestCase):
'file.copy': mock_cp,
'file.manage_file': mock_ex,
'cmd.run_all': mock_cmd_fail}):
comt = ('Must provide name to file.exists')
comt = ('Destination file name is required')
ret.update({'comment': comt, 'name': '', 'pchanges': {}})
self.assertDictEqual(filestate.managed(''), ret)