Merge branch '2018.3.0rc1' into '2018.3'

No conflicts.
This commit is contained in:
rallytime 2018-03-20 09:13:13 -04:00
commit b03cda3cea
No known key found for this signature in database
GPG Key ID: E8F1A4B90D0DEA19
24 changed files with 575 additions and 732 deletions

View File

@ -786,6 +786,35 @@ A value of 10 minutes is a reasonable default.
grains_refresh_every: 0
.. conf_minion:: fibre_channel_grains
``fibre_channel_grains``
------------------------
Default: ``False``
The ``fibre_channel_grains`` setting will enable the ``fc_wwn`` grain for
Fibre Channel WWN's on the minion. Since this grain is expensive, it is
disabled by default.
.. code-block:: yaml
fibre_channel_grains: True
.. conf_minion:: iscsi_grains
``iscsi_grains``
------------------------
Default: ``False``
The ``iscsi_grains`` setting will enable the ``iscsi_iqn`` grain on the
minion. Since this grain is expensive, it is disabled by default.
.. code-block:: yaml
iscsi_grains: True
.. conf_minion:: mine_enabled
``mine_enabled``

View File

@ -263,7 +263,7 @@ The use of ``require_any`` demands that one of the required states executes befo
dependent state. The state containing the ``require_any`` requisite is defined as the
dependent state. The states specified in the ``require_any`` statement are defined as the
required states. If at least one of the required state's execution succeeds, the dependent state
will then execute. If at least one of the required state's execution fails, the dependent state
will then execute. If all of the executions by the required states fail, the dependent state
will not execute.
.. code-block:: yaml

View File

@ -521,6 +521,8 @@ In addition to the ``mapping`` and ``port`` options, the following additional op
match a given Master. If set to ``any`` (the default), then any match to a
key/value mapping will constitute a match.
- ``pause`` - The interval in seconds between attempts (default: 5).
- ``fibre_channel_grains`` - Enables the ``fc_wwn`` grain. (Default: False)
- ``iscsi_grains`` - Enables the ``iscsi_iqn`` grain. (Default: False)
Connection to a type instead of DNS
===================================
@ -1522,7 +1524,7 @@ The use of ``require_any`` demands that one of the required states executes befo
dependent state. The state containing the ``require_any`` requisite is defined as the
dependent state. The states specified in the ``require_any`` statement are defined as the
required states. If at least one of the required state's execution succeeds, the dependent state
will then execute. If at least one of the required state's execution fails, the dependent state
will then execute. If all of the executions by the required states fail, the dependent state
will not execute.
- ``watch_any``

View File

@ -15,7 +15,6 @@ from __future__ import absolute_import, print_function, unicode_literals
import os
import socket
import sys
import glob
import re
import platform
import logging
@ -64,7 +63,6 @@ __salt__ = {
'cmd.run_all': salt.modules.cmdmod._run_all_quiet,
'smbios.records': salt.modules.smbios.records,
'smbios.get': salt.modules.smbios.get,
'cmd.run_ps': salt.modules.cmdmod.powershell,
}
log = logging.getLogger(__name__)
@ -2456,123 +2454,3 @@ def default_gateway():
except Exception:
continue
return grains
def fc_wwn():
'''
Return list of fiber channel HBA WWNs
'''
grains = {}
grains['fc_wwn'] = False
if salt.utils.platform.is_linux():
grains['fc_wwn'] = _linux_wwns()
elif salt.utils.platform.is_windows():
grains['fc_wwn'] = _windows_wwns()
return grains
def iscsi_iqn():
'''
Return iSCSI IQN
'''
grains = {}
grains['iscsi_iqn'] = False
if salt.utils.platform.is_linux():
grains['iscsi_iqn'] = _linux_iqn()
elif salt.utils.platform.is_windows():
grains['iscsi_iqn'] = _windows_iqn()
elif salt.utils.platform.is_aix():
grains['iscsi_iqn'] = _aix_iqn()
return grains
def _linux_iqn():
'''
Return iSCSI IQN from a Linux host.
'''
ret = []
initiator = '/etc/iscsi/initiatorname.iscsi'
try:
with salt.utils.files.fopen(initiator, 'r') as _iscsi:
for line in _iscsi:
line = line.strip()
if line.startswith('InitiatorName='):
ret.append(line.split('=', 1)[1])
except IOError as ex:
if ex.errno != os.errno.ENOENT:
log.debug("Error while accessing '%s': %s", initiator, ex)
return ret
def _aix_iqn():
'''
Return iSCSI IQN from an AIX host.
'''
ret = []
aixcmd = 'lsattr -E -l iscsi0 | grep initiator_name'
aixret = __salt__['cmd.run'](aixcmd)
if aixret[0].isalpha():
try:
ret.append(aixret.split()[1].rstrip())
except IndexError:
pass
return ret
def _linux_wwns():
'''
Return Fibre Channel port WWNs from a Linux host.
'''
ret = []
for fcfile in glob.glob('/sys/class/fc_host/*/port_name'):
with salt.utils.files.fopen(fcfile, 'r') as _wwn:
for line in _wwn:
ret.append(line.rstrip()[2:])
return ret
def _windows_iqn():
'''
Return iSCSI IQN from a Windows host.
'''
ret = []
wmic = salt.utils.path.which('wmic')
if not wmic:
return ret
namespace = r'\\root\WMI'
mspath = 'MSiSCSIInitiator_MethodClass'
get = 'iSCSINodeName'
cmdret = __salt__['cmd.run_all'](
'{0} /namespace:{1} path {2} get {3} /format:table'.format(
wmic, namespace, mspath, get))
for line in cmdret['stdout'].splitlines():
if line.startswith('iqn.'):
line = line.rstrip()
ret.append(line.rstrip())
return ret
def _windows_wwns():
'''
Return Fibre Channel port WWNs from a Windows host.
'''
ps_cmd = r'Get-WmiObject -ErrorAction Stop -class MSFC_FibrePortHBAAttributes -namespace "root\WMI" | Select -Expandproperty Attributes | %{($_.PortWWN | % {"{0:x2}" -f $_}) -join ""}'
ret = []
cmdret = __salt__['cmd.run_ps'](ps_cmd)
for line in cmdret:
ret.append(line.rstrip())
return ret

View File

@ -0,0 +1,76 @@
# -*- coding: utf-8 -*-
'''
Grains for Fibre Channel WWN's. On Windows this runs a PowerShell command that
queries WMI to get the Fibre Channel WWN's available.
.. versionadded:: 2018.3.0
To enable these grains set ``fibre_channel_grains: True``.
.. code-block:: yaml
fibre_channel_grains: True
'''
# Import Python libs
from __future__ import absolute_import, print_function, unicode_literals
import glob
import logging
# Import Salt libs
import salt.modules.cmdmod
import salt.utils.platform
import salt.utils.files
__virtualname__ = 'fibre_channel'
# Get logging started
log = logging.getLogger(__name__)
def __virtual__():
if __opts__.get('fibre_channel_grains', False) is False:
return False
else:
return __virtualname__
def _linux_wwns():
'''
Return Fibre Channel port WWNs from a Linux host.
'''
ret = []
for fc_file in glob.glob('/sys/class/fc_host/*/port_name'):
with salt.utils.files.fopen(fc_file, 'r') as _wwn:
content = _wwn.read()
for line in content.splitlines():
ret.append(line.rstrip()[2:])
return ret
def _windows_wwns():
'''
Return Fibre Channel port WWNs from a Windows host.
'''
ps_cmd = r'Get-WmiObject -ErrorAction Stop ' \
r'-class MSFC_FibrePortHBAAttributes ' \
r'-namespace "root\WMI" | ' \
r'Select -Expandproperty Attributes | ' \
r'%{($_.PortWWN | % {"{0:x2}" -f $_}) -join ""}'
ret = []
cmd_ret = salt.modules.cmdmod.powershell(ps_cmd)
for line in cmd_ret:
ret.append(line.rstrip())
return ret
def fibre_channel_wwns():
'''
Return list of fiber channel HBA WWNs
'''
grains = {'fc_wwn': False}
if salt.utils.platform.is_linux():
grains['fc_wwn'] = _linux_wwns()
elif salt.utils.platform.is_windows():
grains['fc_wwn'] = _windows_wwns()
return grains

114
salt/grains/iscsi.py Normal file
View File

@ -0,0 +1,114 @@
# -*- coding: utf-8 -*-
'''
Grains for iSCSI Qualified Names (IQN).
.. versionadded:: 2018.3.0
To enable these grains set `iscsi_grains: True`.
.. code-block:: yaml
iscsi_grains: True
'''
# Import Python libs
from __future__ import absolute_import, print_function, unicode_literals
import logging
import os
# Import Salt libs
import salt.modules.cmdmod
import salt.utils.files
import salt.utils.path
import salt.utils.platform
__virtualname__ = 'iscsi'
# Get logging started
log = logging.getLogger(__name__)
def __virtual__():
if __opts__.get('iscsi_grains', False) is False:
return False
else:
return __virtualname__
def iscsi_iqn():
'''
Return iSCSI IQN
'''
grains = {}
grains['iscsi_iqn'] = False
if salt.utils.platform.is_linux():
grains['iscsi_iqn'] = _linux_iqn()
elif salt.utils.platform.is_windows():
grains['iscsi_iqn'] = _windows_iqn()
elif salt.utils.platform.is_aix():
grains['iscsi_iqn'] = _aix_iqn()
return grains
def _linux_iqn():
'''
Return iSCSI IQN from a Linux host.
'''
ret = []
initiator = '/etc/iscsi/initiatorname.iscsi'
try:
with salt.utils.files.fopen(initiator, 'r') as _iscsi:
for line in _iscsi:
line = line.strip()
if line.startswith('InitiatorName='):
ret.append(line.split('=', 1)[1])
except IOError as ex:
if ex.errno != os.errno.ENOENT:
log.debug("Error while accessing '%s': %s", initiator, ex)
return ret
def _aix_iqn():
'''
Return iSCSI IQN from an AIX host.
'''
ret = []
aix_cmd = 'lsattr -E -l iscsi0 | grep initiator_name'
aix_ret = salt.modules.cmdmod.run(aix_cmd)
if aix_ret[0].isalpha():
try:
ret.append(aix_ret.split()[1].rstrip())
except IndexError:
pass
return ret
def _windows_iqn():
'''
Return iSCSI IQN from a Windows host.
'''
ret = []
wmic = salt.utils.path.which('wmic')
if not wmic:
return ret
namespace = r'\\root\WMI'
path = 'MSiSCSIInitiator_MethodClass'
get = 'iSCSINodeName'
cmd_ret = salt.modules.cmdmod.run_all(
'{0} /namespace:{1} path {2} get {3} /format:table'
''.format(wmic, namespace, path, get))
for line in cmd_ret['stdout'].splitlines():
if line.startswith('iqn.'):
line = line.rstrip()
ret.append(line.rstrip())
return ret

View File

@ -652,7 +652,7 @@ def _load_cached_grains(opts, cfn):
try:
serial = salt.payload.Serial(opts)
with salt.utils.files.fopen(cfn, 'rb') as fp_:
cached_grains = salt.utils.data.decode(serial.load(fp_))
cached_grains = salt.utils.data.decode(serial.load(fp_), preserve_tuples=True)
if not cached_grains:
log.debug('Cached grains are empty, cache might be corrupted. Refreshing.')
return None
@ -819,7 +819,7 @@ def grains(opts, force_refresh=False, proxy=None):
salt.utils.dictupdate.update(grains_data, opts['grains'])
else:
grains_data.update(opts['grains'])
return salt.utils.data.decode(grains_data)
return salt.utils.data.decode(grains_data, preserve_tuples=True)
# TODO: get rid of? Does anyone use this? You should use raw() instead

View File

@ -90,7 +90,7 @@ def _to_dict(objects):
def db_list(user=None, password=None, host=None, port=None, authdb=None):
'''
List all Mongodb databases
List all MongoDB databases
CLI Example:
@ -112,7 +112,7 @@ def db_list(user=None, password=None, host=None, port=None, authdb=None):
def db_exists(name, user=None, password=None, host=None, port=None, authdb=None):
'''
Checks if a database exists in Mongodb
Checks if a database exists in MongoDB
CLI Example:
@ -130,7 +130,7 @@ def db_exists(name, user=None, password=None, host=None, port=None, authdb=None)
def db_remove(name, user=None, password=None, host=None, port=None, authdb=None):
'''
Remove a Mongodb database
Remove a MongoDB database
CLI Example:
@ -207,7 +207,7 @@ def user_find(name, user=None, password=None, host=None, port=None,
def user_list(user=None, password=None, host=None, port=None, database='admin', authdb=None):
'''
List users of a Mongodb database
List users of a MongoDB database
CLI Example:
@ -248,7 +248,7 @@ def user_list(user=None, password=None, host=None, port=None, database='admin',
def user_exists(name, user=None, password=None, host=None, port=None,
database='admin', authdb=None):
'''
Checks if a user exists in Mongodb
Checks if a user exists in MongoDB
CLI Example:
@ -271,7 +271,7 @@ def user_exists(name, user=None, password=None, host=None, port=None,
def user_create(name, passwd, user=None, password=None, host=None, port=None,
database='admin', authdb=None, roles=None):
'''
Create a Mongodb user
Create a MongoDB user
CLI Example:
@ -299,7 +299,7 @@ def user_create(name, passwd, user=None, password=None, host=None, port=None,
def user_remove(name, user=None, password=None, host=None, port=None,
database='admin', authdb=None):
'''
Remove a Mongodb user
Remove a MongoDB user
CLI Example:
@ -325,7 +325,7 @@ def user_remove(name, user=None, password=None, host=None, port=None,
def user_roles_exists(name, roles, database, user=None, password=None, host=None,
port=None, authdb=None):
'''
Checks if a user of a Mongodb database has specified roles
Checks if a user of a MongoDB database has specified roles
CLI Examples:
@ -363,7 +363,7 @@ def user_roles_exists(name, roles, database, user=None, password=None, host=None
def user_grant_roles(name, roles, database, user=None, password=None, host=None,
port=None, authdb=None):
'''
Grant one or many roles to a Mongodb user
Grant one or many roles to a MongoDB user
CLI Examples:
@ -398,7 +398,7 @@ def user_grant_roles(name, roles, database, user=None, password=None, host=None,
def user_revoke_roles(name, roles, database, user=None, password=None, host=None,
port=None, authdb=None):
'''
Revoke one or many roles to a Mongodb user
Revoke one or many roles to a MongoDB user
CLI Examples:

View File

@ -58,7 +58,7 @@ SCHEDULE_CONF = [
'after',
'return_config',
'return_kwargs',
'run_on_start'
'run_on_start',
'skip_during_range',
'run_after_skip_range',
]

View File

@ -131,6 +131,10 @@ def _new_extension(name, value, critical=0, issuer=None, _pyfree=1):
raise salt.exceptions.SaltInvocationError(
'value must be precomputed hash')
# ensure name and value are bytes
name = salt.utils.stringutils.to_bytes(name)
value = salt.utils.stringutils.to_bytes(value)
try:
ctx = M2Crypto.m2.x509v3_set_nconf()
_fix_ctx(ctx, issuer)
@ -316,7 +320,7 @@ def _text_or_file(input_):
'''
if os.path.isfile(input_):
with salt.utils.files.fopen(input_) as fp_:
return salt.utils.stringutils.to_unicode(fp_.read())
return salt.utils.stringutils.to_bytes(fp_.read())
else:
return input_
@ -493,7 +497,7 @@ def get_pem_entry(text, pem_type=None):
ret += pem_body[i:i + 64] + '\n'
ret += pem_footer + '\n'
return ret
return ret.encode('ascii')
def get_pem_entries(glob_path):

View File

@ -1,435 +0,0 @@
# -*- coding: utf-8 -*-
'''
Management of Mongodb users and databases
=========================================
.. note::
This module requires PyMongo to be installed.
'''
# Import Python libs
from __future__ import absolute_import, print_function, unicode_literals
# Define the module's virtual name
__virtualname__ = 'mongodb'
def __virtual__():
if 'mongodb.user_exists' not in __salt__:
return False
return __virtualname__
def database_absent(name,
user=None,
password=None,
host=None,
port=None,
authdb=None):
'''
Ensure that the named database is absent. Note that creation doesn't make sense in MongoDB.
name
The name of the database to remove
user
The user to connect as (must be able to create the user)
password
The password of the user
host
The host to connect to
port
The port to connect to
authdb
The database in which to authenticate
'''
ret = {'name': name,
'changes': {},
'result': True,
'comment': ''}
#check if database exists and remove it
if __salt__['mongodb.db_exists'](name, user, password, host, port, authdb=authdb):
if __opts__['test']:
ret['result'] = None
ret['comment'] = ('Database {0} is present and needs to be removed'
).format(name)
return ret
if __salt__['mongodb.db_remove'](name, user, password, host, port, authdb=authdb):
ret['comment'] = 'Database {0} has been removed'.format(name)
ret['changes'][name] = 'Absent'
return ret
# fallback
ret['comment'] = ('User {0} is not present, so it cannot be removed'
).format(name)
return ret
def user_present(name,
passwd,
database="admin",
user=None,
password=None,
host="localhost",
port=27017,
authdb=None):
'''
Ensure that the user is present with the specified properties
name
The name of the user to manage
passwd
The password of the user to manage
user
MongoDB user with sufficient privilege to create the user
password
Password for the admin user specified with the ``user`` parameter
host
The hostname/IP address of the MongoDB server
port
The port on which MongoDB is listening
database
The database in which to create the user
.. note::
If the database doesn't exist, it will be created.
authdb
The database in which to authenticate
Example:
.. code-block:: yaml
mongouser-myapp:
mongodb.user_present:
- name: myapp
- passwd: password-of-myapp
# Connect as admin:sekrit
- user: admin
- password: sekrit
'''
ret = {'name': name,
'changes': {},
'result': True,
'comment': 'User {0} is already present'.format(name)}
# Check for valid port
try:
port = int(port)
except TypeError:
ret['result'] = False
ret['comment'] = 'Port ({0}) is not an integer.'.format(port)
return ret
# check if user exists
user_exists = __salt__['mongodb.user_exists'](name, user, password, host, port, database, authdb)
if user_exists is True:
return ret
# if the check does not return a boolean, return an error
# this may be the case if there is a database connection error
if not isinstance(user_exists, bool):
ret['comment'] = user_exists
ret['result'] = False
return ret
if __opts__['test']:
ret['result'] = None
ret['comment'] = ('User {0} is not present and needs to be created'
).format(name)
return ret
# The user is not present, make it!
if __salt__['mongodb.user_create'](name, passwd, user, password, host, port, database=database, authdb=authdb):
ret['comment'] = 'User {0} has been created'.format(name)
ret['changes'][name] = 'Present'
else:
ret['comment'] = 'Failed to create database {0}'.format(name)
ret['result'] = False
return ret
def user_absent(name,
user=None,
password=None,
host=None,
port=None,
database="admin",
authdb=None):
'''
Ensure that the named user is absent
name
The name of the user to remove
user
MongoDB user with sufficient privilege to create the user
password
Password for the admin user specified by the ``user`` parameter
host
The hostname/IP address of the MongoDB server
port
The port on which MongoDB is listening
database
The database from which to remove the user specified by the ``name``
parameter
authdb
The database in which to authenticate
'''
ret = {'name': name,
'changes': {},
'result': True,
'comment': ''}
#check if user exists and remove it
user_exists = __salt__['mongodb.user_exists'](name, user, password, host, port, database=database, authdb=authdb)
if user_exists is True:
if __opts__['test']:
ret['result'] = None
ret['comment'] = ('User {0} is present and needs to be removed'
).format(name)
return ret
if __salt__['mongodb.user_remove'](name, user, password, host, port, database=database, authdb=authdb):
ret['comment'] = 'User {0} has been removed'.format(name)
ret['changes'][name] = 'Absent'
return ret
# if the check does not return a boolean, return an error
# this may be the case if there is a database connection error
if not isinstance(user_exists, bool):
ret['comment'] = user_exists
ret['result'] = False
return ret
# fallback
ret['comment'] = ('User {0} is not present, so it cannot be removed'
).format(name)
return ret
def _roles_to_set(roles, database):
ret = set()
for r in roles:
if isinstance(r, dict):
if r['db'] == database:
ret.add(r['role'])
else:
ret.add(r)
return ret
def _user_roles_to_set(user_list, name, database):
ret = set()
for item in user_list:
if item['user'] == name:
ret = ret.union(_roles_to_set(item['roles'], database))
return ret
def user_grant_roles(name, roles,
database="admin",
user=None,
password=None,
host="localhost",
port=27017,
authdb=None):
'''
Ensure that the named user is granted certain roles
name
The name of the user to remove
roles
The roles to grant to the user
user
MongoDB user with sufficient privilege to create the user
password
Password for the admin user specified by the ``user`` parameter
host
The hostname/IP address of the MongoDB server
port
The port on which MongoDB is listening
database
The database from which to remove the user specified by the ``name``
parameter
authdb
The database in which to authenticate
'''
ret = {'name': name,
'changes': {},
'result': False,
'comment': ''}
if not isinstance(roles, (list, tuple)):
roles = [roles]
if not roles:
ret['result'] = True
ret['comment'] = "nothing to do (no roles given)"
return ret
# Check for valid port
try:
port = int(port)
except TypeError:
ret['result'] = False
ret['comment'] = 'Port ({0}) is not an integer.'.format(port)
return ret
# check if grant exists
user_roles_exists = __salt__['mongodb.user_roles_exists'](name, roles, database,
user=user, password=password, host=host, port=port, authdb=authdb)
if user_roles_exists is True:
ret['result'] = True
ret['comment'] = "Roles already assigned"
return ret
user_list = __salt__['mongodb.user_list'](database=database,
user=user, password=password, host=host, port=port, authdb=authdb)
user_set = _user_roles_to_set(user_list, name, database)
roles_set = _roles_to_set(roles, database)
diff = roles_set - user_set
if __opts__['test']:
ret['result'] = None
ret['comment'] = "Would have modified roles (missing: {0})".format(diff)
return ret
# The user is not present, make it!
if __salt__['mongodb.user_grant_roles'](name, roles, database,
user=user, password=password, host=host, port=port, authdb=authdb):
ret['comment'] = 'Granted roles to {0} on {1}'.format(name, database)
ret['changes'][name] = ['{0} granted'.format(i) for i in diff]
ret['result'] = True
else:
ret['comment'] = 'Failed to grant roles ({2}) to {0} on {1}'.format(name, database, diff)
return ret
def user_set_roles(name, roles,
database="admin",
user=None,
password=None,
host="localhost",
port=27017,
authdb=None):
'''
Ensure that the named user has the given roles and no other roles
name
The name of the user to remove
roles
The roles the given user should have
user
MongoDB user with sufficient privilege to create the user
password
Password for the admin user specified by the ``user`` parameter
host
The hostname/IP address of the MongoDB server
port
The port on which MongoDB is listening
database
The database from which to remove the user specified by the ``name``
parameter
authdb
The database in which to authenticate
'''
ret = {'name': name,
'changes': {},
'result': False,
'comment': ''}
if not isinstance(roles, (list, tuple)):
roles = [roles]
if not roles:
ret['result'] = True
ret['comment'] = "nothing to do (no roles given)"
return ret
# Check for valid port
try:
port = int(port)
except TypeError:
ret['result'] = False
ret['comment'] = 'Port ({0}) is not an integer.'.format(port)
return ret
user_list = __salt__['mongodb.user_list'](database=database,
user=user, password=password, host=host, port=port, authdb=authdb)
user_set = _user_roles_to_set(user_list, name, database)
roles_set = _roles_to_set(roles, database)
to_grant = list(roles_set - user_set)
to_revoke = list(user_set - roles_set)
if not to_grant and not to_revoke:
ret['result'] = True
ret['comment'] = "User {0} has the appropriate roles on {1}".format(name, database)
return ret
if __opts__['test']:
lsg = ', '.join(to_grant)
lsr = ', '.join(to_revoke)
ret['result'] = None
ret['comment'] = "Would have modified roles (grant: {0}; revoke: {1})".format(lsg, lsr)
return ret
ret['changes'][name] = changes = {}
if to_grant:
if not __salt__['mongodb.user_grant_roles'](name, to_grant, database,
user=user, password=password, host=host, port=port, authdb=authdb):
ret['comment'] = "failed to grant some or all of {0} to {1} on {2}".format(to_grant, name, database)
return ret
else:
changes['granted'] = list(to_grant)
if to_revoke:
if not __salt__['mongodb.user_revoke_roles'](name, to_revoke, database,
user=user, password=password, host=host, port=port, authdb=authdb):
ret['comment'] = "failed to revoke some or all of {0} to {1} on {2}".format(to_revoke, name, database)
return ret
else:
changes['revoked'] = list(to_revoke)
ret['result'] = True
return ret

View File

@ -1,14 +1,23 @@
# -*- coding: utf-8 -*-
'''
Management of Mongodb databases
Management of MongoDB Databases
===============================
Only deletion is supported, creation doesn't make sense
and can be done using mongodb_user.present
:depends: - pymongo Python module
Only deletion is supported, creation doesn't make sense and can be done using
:py:func:`mongodb_user.present <salt.states.mongodb_user.present>`.
'''
from __future__ import absolute_import, print_function, unicode_literals
import salt.utils.versions
# Define the module's virtual name
__virtualname__ = 'mongodb_database'
def __virtual__():
if 'mongodb.db_exists' in __salt__:
return __virtualname__
return False
def absent(name,
@ -18,10 +27,8 @@ def absent(name,
port=None,
authdb=None):
'''
.. deprecated:: Fluorine
Use ``mongodb.database_absent`` instead
Ensure that the named database is absent
Ensure that the named database is absent. Note that creation doesn't make
sense in MongoDB.
name
The name of the database to remove
@ -41,19 +48,11 @@ def absent(name,
authdb
The database in which to authenticate
'''
ret = {'name': name,
'changes': {},
'result': True,
'comment': ''}
salt.utils.versions.warn_until(
'Fluorine',
'The \'mongodb_database.absent\' function has been deprecated and will be removed in Salt '
'{version}. Please use \'mongodb.database_absent\' instead.'
)
#check if database exists and remove it
if __salt__['mongodb.db_exists'](name, user, password, host, port, authdb=authdb):
if __opts__['test']:
ret['result'] = None
@ -65,7 +64,5 @@ def absent(name,
ret['changes'][name] = 'Absent'
return ret
# fallback
ret['comment'] = ('User {0} is not present, so it cannot be removed'
).format(name)
ret['comment'] = 'Database {0} is not present'.format(name)
return ret

View File

@ -1,16 +1,12 @@
# -*- coding: utf-8 -*-
'''
Management of Mongodb users
Management of MongoDB Users
===========================
.. note::
This module requires PyMongo to be installed.
:depends: - pymongo Python module
'''
from __future__ import absolute_import, print_function, unicode_literals
import salt.utils.versions
# Define the module's virtual name
__virtualname__ = 'mongodb_user'
@ -31,9 +27,6 @@ def present(name,
authdb=None,
roles=None):
'''
.. deprecated:: Fluorine
Use ``mongodb.user_present`` instead
Ensure that the user is present with the specified properties
name
@ -84,13 +77,6 @@ def present(name,
- dbOwner
'''
salt.utils.versions.warn_until(
'Fluorine',
'The \'mongodb_user.present\' function has been deprecated and will be removed in Salt '
'{version}. Please use \'mongodb.user_present\' instead.'
)
ret = {'name': name,
'changes': {},
'result': True,
@ -167,9 +153,6 @@ def absent(name,
database="admin",
authdb=None):
'''
.. deprecated:: Fluorine
Use ``mongodb.user_absent`` instead
Ensure that the named user is absent
name
@ -194,13 +177,6 @@ def absent(name,
authdb
The database in which to authenticate
'''
salt.utils.versions.warn_until(
'Fluorine',
'The \'mongodb_user.absent\' function has been deprecated and will be removed in Salt '
'{version}. Please use \'mongodb.user_absent\' instead.'
)
ret = {'name': name,
'changes': {},
'result': True,
@ -227,6 +203,5 @@ def absent(name,
return ret
# fallback
ret['comment'] = ('User {0} is not present, so it cannot be removed'
).format(name)
ret['comment'] = 'User {0} is not present'.format(name)
return ret

View File

@ -310,7 +310,7 @@ def private_key_managed(name,
ret = __states__['file.managed'](**file_args)
if ret['changes'] and new_key:
ret['changes'] = 'New private key generated'
ret['changes'] = {'new': 'New private key generated'}
return ret

View File

@ -19,14 +19,28 @@ from salt.ext import six
log = logging.getLogger(__name__)
def __split(raw):
'''
Performs a splitlines on the string. This function exists to make mocking
possible in unit tests, since the member functions of the str/unicode
builtins cannot be mocked.
'''
return raw.splitlines()
def find_json(raw):
'''
Pass in a raw string and load the json when it starts. This allows for a
string to start with garbage and end with json but be cleanly loaded
'''
ret = {}
for ind, _ in enumerate(raw):
working = '\n'.join(raw.splitlines()[ind:])
lines = __split(raw)
for ind, _ in enumerate(lines):
try:
working = '\n'.join(lines[ind:])
except UnicodeDecodeError:
working = '\n'.join(salt.utils.data.decode(lines[ind:]))
try:
ret = json.loads(working) # future lint: blacklisted-function
except ValueError:

View File

@ -1241,7 +1241,7 @@ class Schedule(object):
# If there is no job specific skip_during_range available,
# grab the global which defaults to None.
if 'skip_during_range' not in data:
if 'skip_during_range' not in data and self.skip_during_range:
data['skip_during_range'] = self.skip_during_range
if 'skip_during_range' in data and data['skip_during_range']:

View File

@ -368,3 +368,32 @@ class SchedulerEvalTest(ModuleCase, SaltReturnAssertsMixin):
self.schedule.eval(now=run_time)
ret = self.schedule.job_status('job1')
self.assertEqual(ret['_last_run'], run_time)
def test_eval_run_on_start(self):
'''
verify that scheduled job is run when minion starts
'''
job = {
'schedule': {
'job1': {
'function': 'test.ping',
'hours': '1',
'run_on_start': True
}
}
}
# Add job to schedule
self.schedule.opts.update(job)
# eval at 2:00pm, will run.
run_time = dateutil_parser.parse('11/29/2017 2:00pm')
self.schedule.eval(now=run_time)
ret = self.schedule.job_status('job1')
self.assertEqual(ret['_last_run'], run_time)
# eval at 3:00pm, will run.
run_time = dateutil_parser.parse('11/29/2017 3:00pm')
self.schedule.eval(now=run_time)
ret = self.schedule.job_status('job1')
self.assertEqual(ret['_last_run'], run_time)

View File

@ -558,58 +558,6 @@ PATCHLEVEL = 3
}
self._run_os_grains_tests("ubuntu-17.10", _os_release_map, expectation)
def test_windows_iscsi_iqn_grains(self):
cmd_run_mock = MagicMock(
return_value={'stdout': 'iSCSINodeName\niqn.1991-05.com.microsoft:simon-x1\n'}
)
with patch.object(salt.utils.platform, 'is_linux',
MagicMock(return_value=False)):
with patch.object(salt.utils.platform, 'is_windows',
MagicMock(return_value=True)):
with patch.dict(core.__salt__, {'run_all': cmd_run_mock}):
with patch.object(salt.utils.path, 'which',
MagicMock(return_value=True)):
with patch.dict(core.__salt__, {'cmd.run_all': cmd_run_mock}):
_grains = core.iscsi_iqn()
self.assertEqual(_grains.get('iscsi_iqn'),
['iqn.1991-05.com.microsoft:simon-x1'])
@skipIf(salt.utils.platform.is_windows(), 'System is Windows')
def test_aix_iscsi_iqn_grains(self):
cmd_run_mock = MagicMock(
return_value='initiator_name iqn.localhost.hostid.7f000001'
)
with patch.object(salt.utils.platform, 'is_linux',
MagicMock(return_value=False)):
with patch.object(salt.utils.platform, 'is_aix',
MagicMock(return_value=True)):
with patch.dict(core.__salt__, {'cmd.run': cmd_run_mock}):
_grains = core.iscsi_iqn()
self.assertEqual(_grains.get('iscsi_iqn'),
['iqn.localhost.hostid.7f000001'])
@patch('salt.grains.core.os.path.isfile', MagicMock(return_value=True))
@patch('salt.grains.core.os.access', MagicMock(return_value=True))
def test_linux_iscsi_iqn_grains(self):
_iscsi_file = '## DO NOT EDIT OR REMOVE THIS FILE!\n' \
'## If you remove this file, the iSCSI daemon will not start.\n' \
'## If you change the InitiatorName, existing access control lists\n' \
'## may reject this initiator. The InitiatorName must be unique\n' \
'## for each iSCSI initiator. Do NOT duplicate iSCSI InitiatorNames.\n' \
'InitiatorName=iqn.1993-08.org.debian:01:d12f7aba36\n'
with patch('salt.utils.files.fopen', mock_open()) as iscsi_initiator_file:
iscsi_initiator_file.return_value.__iter__.return_value = _iscsi_file.splitlines()
iqn = core._linux_iqn()
assert isinstance(iqn, list)
assert len(iqn) == 1
assert iqn == ['iqn.1993-08.org.debian:01:d12f7aba36']
@skipIf(not salt.utils.platform.is_linux(), 'System is not Linux')
def test_linux_memdata(self):
'''
@ -906,30 +854,3 @@ SwapTotal: 4789244 kB'''
[]}}
with patch.object(salt.utils.dns, 'parse_resolv', MagicMock(return_value=resolv_mock)):
assert core.dns() == ret
@patch('salt.utils.files.fopen', MagicMock(side_effect=IOError(os.errno.EPERM,
'The cables are not the same length.')))
@patch('salt.grains.core.log', MagicMock())
def test_linux_iqn_non_root(self):
'''
Test if linux_iqn is running on salt-master as non-root
and handling access denial properly.
:return:
'''
assert core._linux_iqn() == []
core.log.debug.assert_called()
assert 'Error while accessing' in core.log.debug.call_args[0][0]
assert 'cables are not the same' in core.log.debug.call_args[0][2].strerror
assert core.log.debug.call_args[0][2].errno == os.errno.EPERM
assert core.log.debug.call_args[0][1] == '/etc/iscsi/initiatorname.iscsi'
@patch('salt.utils.files.fopen', MagicMock(side_effect=IOError(os.errno.ENOENT, '')))
@patch('salt.grains.core.log', MagicMock())
def test_linux_iqn_no_iscsii_initiator(self):
'''
Test if linux_iqn is running on salt-master as root.
iscsii initiator is not there accessible or is not supported.
:return:
'''
assert core._linux_iqn() == []
core.log.debug.assert_not_called()

View File

@ -0,0 +1,51 @@
# -*- coding: utf-8 -*-
'''
:codeauthor: :email:`Shane Lee <slee@saltstack.com>`
'''
# Import Python libs
from __future__ import absolute_import, print_function, unicode_literals
# Import Salt Testing Libs
from tests.support.unit import TestCase, skipIf
from tests.support.mock import (
patch,
mock_open,
MagicMock,
NO_MOCK,
NO_MOCK_REASON
)
# Import Salt Libs
import salt.grains.fibre_channel as fibre_channel
@skipIf(NO_MOCK, NO_MOCK_REASON)
class FibreChannelGrainsTestCase(TestCase):
'''
Test cases for iscsi grains
'''
def test_windows_fibre_channel_wwns_grains(self):
wwns = ['20:00:00:25:b5:11:11:4c',
'20:00:00:25:b5:11:11:5c',
'20:00:00:25:b5:44:44:4c',
'20:00:00:25:b5:44:44:5c']
cmd_run_mock = MagicMock(return_value=wwns)
with patch('salt.modules.cmdmod.powershell', cmd_run_mock):
ret = fibre_channel._windows_wwns()
self.assertEqual(ret, wwns)
def test_linux_fibre_channel_wwns_grains(self):
def multi_mock_open(*file_contents):
mock_files = [mock_open(read_data=content).return_value for content in file_contents]
mock_opener = mock_open()
mock_opener.side_effect = mock_files
return mock_opener
files = ['file1', 'file2']
with patch('glob.glob', MagicMock(return_value=files)):
with patch('salt.utils.files.fopen', multi_mock_open('0x500143802426baf4', '0x500143802426baf5')):
ret = fibre_channel._linux_wwns()
self.assertEqual(ret, ['500143802426baf4', '500143802426baf5'])

View File

@ -0,0 +1,94 @@
# -*- coding: utf-8 -*-
'''
:codeauthor: :email:`Shane Lee <slee@saltstack.com>`
'''
# Import Python libs
from __future__ import absolute_import, print_function, unicode_literals
import os
# Import Salt Testing Libs
from tests.support.unit import TestCase, skipIf
from tests.support.mock import (
patch,
mock_open,
MagicMock,
NO_MOCK,
NO_MOCK_REASON
)
# Import Salt Libs
import salt.grains.iscsi as iscsi
@skipIf(NO_MOCK, NO_MOCK_REASON)
class IscsiGrainsTestCase(TestCase):
'''
Test cases for iscsi grains
'''
def test_windows_iscsi_iqn_grains(self):
cmd_run_mock = MagicMock(
return_value={'stdout': 'iSCSINodeName\n'
'iqn.1991-05.com.microsoft:simon-x1\n'}
)
_grains = {}
with patch('salt.utils.path.which', MagicMock(return_value=True)):
with patch('salt.modules.cmdmod.run_all', cmd_run_mock):
_grains['iscsi_iqn'] = iscsi._windows_iqn()
self.assertEqual(_grains.get('iscsi_iqn'),
['iqn.1991-05.com.microsoft:simon-x1'])
def test_aix_iscsi_iqn_grains(self):
cmd_run_mock = MagicMock(
return_value='initiator_name iqn.localhost.hostid.7f000001'
)
_grains = {}
with patch('salt.modules.cmdmod.run', cmd_run_mock):
_grains['iscsi_iqn'] = iscsi._aix_iqn()
self.assertEqual(_grains.get('iscsi_iqn'),
['iqn.localhost.hostid.7f000001'])
def test_linux_iscsi_iqn_grains(self):
_iscsi_file = '## DO NOT EDIT OR REMOVE THIS FILE!\n' \
'## If you remove this file, the iSCSI daemon will not start.\n' \
'## If you change the InitiatorName, existing access control lists\n' \
'## may reject this initiator. The InitiatorName must be unique\n' \
'## for each iSCSI initiator. Do NOT duplicate iSCSI InitiatorNames.\n' \
'InitiatorName=iqn.1993-08.org.debian:01:d12f7aba36\n'
with patch('salt.utils.files.fopen', mock_open()) as iscsi_initiator_file:
iscsi_initiator_file.return_value.__iter__.return_value = _iscsi_file.splitlines()
iqn = iscsi._linux_iqn()
assert isinstance(iqn, list)
assert len(iqn) == 1
assert iqn == ['iqn.1993-08.org.debian:01:d12f7aba36']
@patch('salt.utils.files.fopen', MagicMock(side_effect=IOError(os.errno.EPERM,
'The cables are not the same length.')))
@patch('salt.grains.iscsi.log', MagicMock())
def test_linux_iqn_non_root(self):
'''
Test if linux_iqn is running on salt-master as non-root
and handling access denial properly.
:return:
'''
assert iscsi._linux_iqn() == []
iscsi.log.debug.assert_called()
assert 'Error while accessing' in iscsi.log.debug.call_args[0][0]
assert 'cables are not the same' in iscsi.log.debug.call_args[0][2].strerror
assert iscsi.log.debug.call_args[0][2].errno == os.errno.EPERM
assert iscsi.log.debug.call_args[0][1] == '/etc/iscsi/initiatorname.iscsi'
@patch('salt.utils.files.fopen', MagicMock(side_effect=IOError(os.errno.ENOENT, '')))
@patch('salt.grains.iscsi.log', MagicMock())
def test_linux_iqn_no_iscsii_initiator(self):
'''
Test if linux_iqn is running on salt-master as root.
iscsii initiator is not there accessible or is not supported.
:return:
'''
assert iscsi._linux_iqn() == []
iscsi.log.debug.assert_not_called()

View File

@ -34,6 +34,12 @@ from tests.support.mock import (
from salt.modules import x509
try:
import M2Crypto # pylint: disable=unused-import
HAS_M2CRYPTO = True
except ImportError:
HAS_M2CRYPTO = False
@skipIf(NO_MOCK, NO_MOCK_REASON)
@skipIf(not bool(pytest), False)
@ -65,3 +71,96 @@ class X509TestCase(TestCase, LoaderModuleMockMixin):
assert x509.log.trace.call_args[0][0] == "Missing attribute '%s'. Error: %s"
assert x509.log.trace.call_args[0][1] == list(subj.nid.keys())[0]
assert isinstance(x509.log.trace.call_args[0][2], TypeError)
@skipIf(not HAS_M2CRYPTO, 'Skipping, M2Crypt is unavailble')
def test_get_pem_entry(self):
'''
Test private function _parse_subject(subject) it handles a missing fields
:return:
'''
ca_key = '''-----BEGIN RSA PRIVATE KEY-----
MIICWwIBAAKBgQCjdjbgL4kQ8Lu73xeRRM1q3C3K3ptfCLpyfw38LRnymxaoJ6ls
pNSx2dU1uJ89YKFlYLo1QcEk4rJ2fdIjarV0kuNCY3rC8jYUp9BpAU5Z6p9HKeT1
2rTPH81JyjbQDR5PyfCyzYOQtpwpB4zIUUK/Go7tTm409xGKbbUFugJNgQIDAQAB
AoGAF24we34U1ZrMLifSRv5nu3OIFNZHyx2DLDpOFOGaII5edwgIXwxZeIzS5Ppr
yO568/8jcdLVDqZ4EkgCwRTgoXRq3a1GLHGFmBdDNvWjSTTMLoozuM0t2zjRmIsH
hUd7tnai9Lf1Bp5HlBEhBU2gZWk+SXqLvxXe74/+BDAj7gECQQDRw1OPsrgTvs3R
3MNwX6W8+iBYMTGjn6f/6rvEzUs/k6rwJluV7n8ISNUIAxoPy5g5vEYK6Ln/Ttc7
u0K1KNlRAkEAx34qcxjuswavL3biNGE+8LpDJnJx1jaNWoH+ObuzYCCVMusdT2gy
kKuq9ytTDgXd2qwZpIDNmscvReFy10glMQJAXebMz3U4Bk7SIHJtYy7OKQzn0dMj
35WnRV81c2Jbnzhhu2PQeAvt/i1sgEuzLQL9QEtSJ6wLJ4mJvImV0TdaIQJAAYyk
TcKK0A8kOy0kMp3yvDHmJZ1L7wr7bBGIZPBlQ0Ddh8i1sJExm1gJ+uN2QKyg/XrK
tDFf52zWnCdVGgDwcQJALW/WcbSEK+JVV6KDJYpwCzWpKIKpBI0F6fdCr1G7Xcwj
c9bcgp7D7xD+TxWWNj4CSXEccJgGr91StV+gFg4ARQ==
-----END RSA PRIVATE KEY-----
'''
ret = x509.get_pem_entry(ca_key)
self.assertEqual(ret, ca_key)
@skipIf(not HAS_M2CRYPTO, 'Skipping, M2Crypt is unavailble')
def test_get_private_key_size(self):
'''
Test private function _parse_subject(subject) it handles a missing fields
:return:
'''
ca_key = '''
-----BEGIN RSA PRIVATE KEY-----
MIICWwIBAAKBgQCjdjbgL4kQ8Lu73xeRRM1q3C3K3ptfCLpyfw38LRnymxaoJ6ls
pNSx2dU1uJ89YKFlYLo1QcEk4rJ2fdIjarV0kuNCY3rC8jYUp9BpAU5Z6p9HKeT1
2rTPH81JyjbQDR5PyfCyzYOQtpwpB4zIUUK/Go7tTm409xGKbbUFugJNgQIDAQAB
AoGAF24we34U1ZrMLifSRv5nu3OIFNZHyx2DLDpOFOGaII5edwgIXwxZeIzS5Ppr
yO568/8jcdLVDqZ4EkgCwRTgoXRq3a1GLHGFmBdDNvWjSTTMLoozuM0t2zjRmIsH
hUd7tnai9Lf1Bp5HlBEhBU2gZWk+SXqLvxXe74/+BDAj7gECQQDRw1OPsrgTvs3R
3MNwX6W8+iBYMTGjn6f/6rvEzUs/k6rwJluV7n8ISNUIAxoPy5g5vEYK6Ln/Ttc7
u0K1KNlRAkEAx34qcxjuswavL3biNGE+8LpDJnJx1jaNWoH+ObuzYCCVMusdT2gy
kKuq9ytTDgXd2qwZpIDNmscvReFy10glMQJAXebMz3U4Bk7SIHJtYy7OKQzn0dMj
35WnRV81c2Jbnzhhu2PQeAvt/i1sgEuzLQL9QEtSJ6wLJ4mJvImV0TdaIQJAAYyk
TcKK0A8kOy0kMp3yvDHmJZ1L7wr7bBGIZPBlQ0Ddh8i1sJExm1gJ+uN2QKyg/XrK
tDFf52zWnCdVGgDwcQJALW/WcbSEK+JVV6KDJYpwCzWpKIKpBI0F6fdCr1G7Xcwj
c9bcgp7D7xD+TxWWNj4CSXEccJgGr91StV+gFg4ARQ==
-----END RSA PRIVATE KEY-----
'''
ret = x509.get_private_key_size(ca_key)
self.assertEqual(ret, 1024)
@skipIf(not HAS_M2CRYPTO, 'Skipping, M2Crypt is unavailble')
def test_create_certificate(self):
'''
Test private function _parse_subject(subject) it handles a missing fields
:return:
'''
ca_key = '''
-----BEGIN RSA PRIVATE KEY-----
MIICWwIBAAKBgQCjdjbgL4kQ8Lu73xeRRM1q3C3K3ptfCLpyfw38LRnymxaoJ6ls
pNSx2dU1uJ89YKFlYLo1QcEk4rJ2fdIjarV0kuNCY3rC8jYUp9BpAU5Z6p9HKeT1
2rTPH81JyjbQDR5PyfCyzYOQtpwpB4zIUUK/Go7tTm409xGKbbUFugJNgQIDAQAB
AoGAF24we34U1ZrMLifSRv5nu3OIFNZHyx2DLDpOFOGaII5edwgIXwxZeIzS5Ppr
yO568/8jcdLVDqZ4EkgCwRTgoXRq3a1GLHGFmBdDNvWjSTTMLoozuM0t2zjRmIsH
hUd7tnai9Lf1Bp5HlBEhBU2gZWk+SXqLvxXe74/+BDAj7gECQQDRw1OPsrgTvs3R
3MNwX6W8+iBYMTGjn6f/6rvEzUs/k6rwJluV7n8ISNUIAxoPy5g5vEYK6Ln/Ttc7
u0K1KNlRAkEAx34qcxjuswavL3biNGE+8LpDJnJx1jaNWoH+ObuzYCCVMusdT2gy
kKuq9ytTDgXd2qwZpIDNmscvReFy10glMQJAXebMz3U4Bk7SIHJtYy7OKQzn0dMj
35WnRV81c2Jbnzhhu2PQeAvt/i1sgEuzLQL9QEtSJ6wLJ4mJvImV0TdaIQJAAYyk
TcKK0A8kOy0kMp3yvDHmJZ1L7wr7bBGIZPBlQ0Ddh8i1sJExm1gJ+uN2QKyg/XrK
tDFf52zWnCdVGgDwcQJALW/WcbSEK+JVV6KDJYpwCzWpKIKpBI0F6fdCr1G7Xcwj
c9bcgp7D7xD+TxWWNj4CSXEccJgGr91StV+gFg4ARQ==
-----END RSA PRIVATE KEY-----
'''
ret = x509.create_certificate(text=True,
signing_private_key=ca_key,
CN='Redacted Root CA',
O='Redacted',
C='BE',
ST='Antwerp',
L='Local Town',
Email='certadm@example.org',
basicConstraints="critical CA:true",
keyUsage="critical cRLSign, keyCertSign",
subjectKeyIdentifier='hash',
authorityKeyIdentifier='keyid,issuer:always',
days_valid=3650,
days_remaining=0)
self.assertIn('BEGIN CERTIFICATE', ret)

View File

@ -56,7 +56,6 @@ class MongodbDatabaseTestCase(TestCase, LoaderModuleMockMixin):
'changes': {'mydb': 'Absent'}})
self.assertDictEqual(mongodb_database.absent(name), ret)
comt = ('User {0} is not present, so it cannot be removed'
.format(name))
comt = 'Database {0} is not present'.format(name)
ret.update({'comment': comt, 'changes': {}})
self.assertDictEqual(mongodb_database.absent(name), ret)

View File

@ -98,7 +98,6 @@ class MongodbUserTestCase(TestCase, LoaderModuleMockMixin):
'changes': {name: 'Absent'}})
self.assertDictEqual(mongodb_user.absent(name), ret)
comt = ('User {0} is not present, so it cannot be removed'
.format(name))
comt = 'User {0} is not present'.format(name)
ret.update({'comment': comt, 'result': True, 'changes': {}})
self.assertDictEqual(mongodb_user.absent(name), ret)

View File

@ -4,37 +4,19 @@ Tests for salt.utils.json
'''
# Import Python libs
from __future__ import absolute_import, print_function, unicode_literals
import errno
import functools
import os
import textwrap
# Import Salt Testing libs
from tests.support.paths import TMP
from tests.support.unit import TestCase, LOREM_IPSUM
from tests.support.helpers import with_tempfile
from tests.support.mock import patch, MagicMock, NO_MOCK, NO_MOCK_REASON
from tests.support.unit import TestCase, LOREM_IPSUM, skipIf
# Import Salt libs
import salt.utils.files
import salt.utils.json
import salt.utils.platform
import salt.utils.stringutils
def with_tempfile(func):
'''
Generate a temp directory for a test
'''
@functools.wraps(func)
def wrapper(self, *args, **kwargs):
temp_file = salt.utils.files.mkstemp(dir=TMP)
try:
return func(self, temp_file, *args, **kwargs)
finally:
try:
os.remove(temp_file)
except OSError as exc:
if exc.errno != errno.ENOENT:
raise
return wrapper
from salt.ext import six
class JSONTestCase(TestCase):
@ -113,6 +95,21 @@ class JSONTestCase(TestCase):
# Test to see if a ValueError is raised if no JSON is passed in
self.assertRaises(ValueError, salt.utils.json.find_json, LOREM_IPSUM)
@skipIf(salt.utils.platform.is_windows(), 'skip until we figure out what to do about decoding unicode on windows')
@skipIf(not six.PY2, 'Test only needed on Python 2')
@skipIf(NO_MOCK, NO_MOCK_REASON)
def test_find_json_unicode_splitlines(self):
'''
Tests a case in salt-ssh where a unicode string is split into a list of
str types by .splitlines().
'''
raw = '{"foo": "öäü"}'
mock_split = MagicMock(return_value=[raw.encode('utf8')])
with patch.object(salt.utils.json, '__split', mock_split):
ret = salt.utils.json.find_json(raw)
self.assertEqual(ret, {'foo': 'öäü'})
def test_dumps_loads(self):
'''
Test dumping to and loading from a string