diff --git a/doc/ref/configuration/minion.rst b/doc/ref/configuration/minion.rst index 49a905a668..c9010a702b 100644 --- a/doc/ref/configuration/minion.rst +++ b/doc/ref/configuration/minion.rst @@ -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`` diff --git a/doc/ref/states/requisites.rst b/doc/ref/states/requisites.rst index f55fd6c031..15242a7b77 100644 --- a/doc/ref/states/requisites.rst +++ b/doc/ref/states/requisites.rst @@ -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 diff --git a/doc/topics/releases/2018.3.0.rst b/doc/topics/releases/2018.3.0.rst index 3444a0f226..2a16022471 100644 --- a/doc/topics/releases/2018.3.0.rst +++ b/doc/topics/releases/2018.3.0.rst @@ -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`` diff --git a/salt/grains/core.py b/salt/grains/core.py index ba15a30bb6..308d02921d 100644 --- a/salt/grains/core.py +++ b/salt/grains/core.py @@ -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 diff --git a/salt/grains/fibre_channel.py b/salt/grains/fibre_channel.py new file mode 100644 index 0000000000..5396bbde7a --- /dev/null +++ b/salt/grains/fibre_channel.py @@ -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 diff --git a/salt/grains/iscsi.py b/salt/grains/iscsi.py new file mode 100644 index 0000000000..80d239f2bd --- /dev/null +++ b/salt/grains/iscsi.py @@ -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 diff --git a/salt/loader.py b/salt/loader.py index 2e8a94d69f..110053b68d 100644 --- a/salt/loader.py +++ b/salt/loader.py @@ -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 diff --git a/salt/modules/mongodb.py b/salt/modules/mongodb.py index a752159961..bf330eb75d 100644 --- a/salt/modules/mongodb.py +++ b/salt/modules/mongodb.py @@ -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: diff --git a/salt/modules/schedule.py b/salt/modules/schedule.py index a77693e66f..0491ee791c 100644 --- a/salt/modules/schedule.py +++ b/salt/modules/schedule.py @@ -58,7 +58,7 @@ SCHEDULE_CONF = [ 'after', 'return_config', 'return_kwargs', - 'run_on_start' + 'run_on_start', 'skip_during_range', 'run_after_skip_range', ] diff --git a/salt/modules/x509.py b/salt/modules/x509.py index cfbb27d3f4..7eed1d27b9 100644 --- a/salt/modules/x509.py +++ b/salt/modules/x509.py @@ -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): diff --git a/salt/states/mongodb.py b/salt/states/mongodb.py deleted file mode 100644 index 1440d6d93a..0000000000 --- a/salt/states/mongodb.py +++ /dev/null @@ -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 diff --git a/salt/states/mongodb_database.py b/salt/states/mongodb_database.py index 08e3ea950e..579f188e4a 100644 --- a/salt/states/mongodb_database.py +++ b/salt/states/mongodb_database.py @@ -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 `. ''' - 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 diff --git a/salt/states/mongodb_user.py b/salt/states/mongodb_user.py index e10b6b1873..42a86eea68 100644 --- a/salt/states/mongodb_user.py +++ b/salt/states/mongodb_user.py @@ -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 diff --git a/salt/states/x509.py b/salt/states/x509.py index e8ba08905b..b2de03d281 100644 --- a/salt/states/x509.py +++ b/salt/states/x509.py @@ -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 diff --git a/salt/utils/json.py b/salt/utils/json.py index 788505cb25..a578b8f843 100644 --- a/salt/utils/json.py +++ b/salt/utils/json.py @@ -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: diff --git a/salt/utils/schedule.py b/salt/utils/schedule.py index 4c997392f7..b9081bf0df 100644 --- a/salt/utils/schedule.py +++ b/salt/utils/schedule.py @@ -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']: diff --git a/tests/integration/scheduler/test_eval.py b/tests/integration/scheduler/test_eval.py index 7d24fb1340..76843665d8 100644 --- a/tests/integration/scheduler/test_eval.py +++ b/tests/integration/scheduler/test_eval.py @@ -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) diff --git a/tests/unit/grains/test_core.py b/tests/unit/grains/test_core.py index 9d0827a08a..5ba485dbbf 100644 --- a/tests/unit/grains/test_core.py +++ b/tests/unit/grains/test_core.py @@ -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() diff --git a/tests/unit/grains/test_fibre_channel.py b/tests/unit/grains/test_fibre_channel.py new file mode 100644 index 0000000000..4b75c1d73b --- /dev/null +++ b/tests/unit/grains/test_fibre_channel.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- +''' + :codeauthor: :email:`Shane Lee ` +''' +# 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']) diff --git a/tests/unit/grains/test_iscsi.py b/tests/unit/grains/test_iscsi.py new file mode 100644 index 0000000000..9ca4ad4809 --- /dev/null +++ b/tests/unit/grains/test_iscsi.py @@ -0,0 +1,94 @@ +# -*- coding: utf-8 -*- +''' + :codeauthor: :email:`Shane Lee ` +''' +# 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() diff --git a/tests/unit/modules/test_x509.py b/tests/unit/modules/test_x509.py index edafe4ff44..a56cc4a9da 100644 --- a/tests/unit/modules/test_x509.py +++ b/tests/unit/modules/test_x509.py @@ -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) diff --git a/tests/unit/states/test_mongodb_database.py b/tests/unit/states/test_mongodb_database.py index 7b306fbcc0..1f318aa44d 100644 --- a/tests/unit/states/test_mongodb_database.py +++ b/tests/unit/states/test_mongodb_database.py @@ -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) diff --git a/tests/unit/states/test_mongodb_user.py b/tests/unit/states/test_mongodb_user.py index 3610fc8f7a..a162b9fcea 100644 --- a/tests/unit/states/test_mongodb_user.py +++ b/tests/unit/states/test_mongodb_user.py @@ -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) diff --git a/tests/unit/utils/test_json.py b/tests/unit/utils/test_json.py index 12ca1c37a1..20220459a3 100644 --- a/tests/unit/utils/test_json.py +++ b/tests/unit/utils/test_json.py @@ -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