Merge "Remove color formatting from the output of rabbitmqctl cluster_status"
diff --git a/_modules/rabbitmq_custom.py b/_modules/rabbitmq_custom.py
new file mode 100644
index 0000000..182ca49
--- /dev/null
+++ b/_modules/rabbitmq_custom.py
@@ -0,0 +1,190 @@
+# -*- coding: utf-8 -*-
+
+# This file is copy of original /usr/lib/python2.7/dist-packages/salt/modules/rabbitmq.py
+# with fix applied for PROD-35125 - rabbitmq module incompatible with rabbitmq-server 3.8+
+# It fix native check_password function by override it here and call it from
+# _states/rabbitmq_user_common.py which also introduced in this patch.
+# Fix source: https://github.com/saltstack/salt/commit/d0e0ed6b60f4de6d2b2551cad2fc7a553efe0274
+
+from __future__ import absolute_import
+
+# Import python libs
+import json
+import re
+import logging
+import os
+import os.path
+import random
+import string
+
+# Import salt libs
+import salt.utils
+import salt.utils.itertools
+import salt.ext.six as six
+from salt.exceptions import SaltInvocationError
+from salt.ext.six.moves import range
+from salt.exceptions import CommandExecutionError
+
+log = logging.getLogger(__name__)
+
+RABBITMQCTL = None
+RABBITMQ_PLUGINS = None
+
+
+def __virtual__():
+ '''
+ Verify RabbitMQ is installed.
+ '''
+ global RABBITMQCTL
+ global RABBITMQ_PLUGINS
+
+ if salt.utils.is_windows():
+ from salt.ext.six.moves import winreg
+ key = None
+ try:
+ key = winreg.OpenKeyEx(
+ winreg.HKEY_LOCAL_MACHINE,
+ 'SOFTWARE\\VMware, Inc.\\RabbitMQ Server',
+ 0,
+ winreg.KEY_READ | winreg.KEY_WOW64_32KEY
+ )
+ (dir_path, value_type) = winreg.QueryValueEx(
+ key,
+ 'Install_Dir'
+ )
+ if value_type != winreg.REG_SZ:
+ raise TypeError('Invalid RabbitMQ Server directory type: {0}'.format(value_type))
+ if not os.path.isdir(dir_path):
+ raise IOError('RabbitMQ directory not found: {0}'.format(dir_path))
+ subdir_match = ''
+ for name in os.listdir(dir_path):
+ if name.startswith('rabbitmq_server-'):
+ subdir_path = os.path.join(dir_path, name)
+ # Get the matching entry that is last in ASCII order.
+ if os.path.isdir(subdir_path) and subdir_path > subdir_match:
+ subdir_match = subdir_path
+ if not subdir_match:
+ raise IOError('"rabbitmq_server-*" subdirectory not found in: {0}'.format(dir_path))
+ RABBITMQCTL = os.path.join(subdir_match, 'sbin', 'rabbitmqctl.bat')
+ RABBITMQ_PLUGINS = os.path.join(subdir_match, 'sbin', 'rabbitmq-plugins.bat')
+ except Exception:
+ pass
+ finally:
+ if key is not None:
+ winreg.CloseKey(key)
+ else:
+ RABBITMQCTL = salt.utils.which('rabbitmqctl')
+ RABBITMQ_PLUGINS = salt.utils.which('rabbitmq-plugins')
+
+ if not RABBITMQCTL:
+ return (False, 'Module rabbitmq: module only works when RabbitMQ is installed')
+ return True
+
+
+def _format_response(response, msg):
+ if isinstance(response, dict):
+ if response['retcode'] != 0 or response['stderr']:
+ raise CommandExecutionError(
+ 'RabbitMQ command failed: {0}'.format(response['stderr'])
+ )
+ else:
+ response = response['stdout']
+ else:
+ if 'Error' in response:
+ raise CommandExecutionError(
+ 'RabbitMQ command failed: {0}'.format(response)
+ )
+ return {
+ msg: response
+ }
+
+
+def check_password(name, password, runas=None):
+ '''
+ .. versionadded:: 2016.3.0
+
+ Checks if a user's password is valid.
+
+ CLI Example:
+
+ .. code-block:: bash
+
+ salt '*' rabbitmq.check_password rabbit_user password
+ '''
+ # try to get the rabbitmq-version - adapted from _get_rabbitmq_plugin
+
+ if runas is None and not salt.utils.is_windows():
+ runas = salt.utils.get_user()
+
+ try:
+ res = __salt__['cmd.run']([RABBITMQCTL, 'status'], reset_system_locale=False, runas=runas, python_shell=False)
+
+ # Fix for PROD-35125 - rabbitmq module incompatible with rabbitmq-server 3.8+
+ # https://github.com/saltstack/salt/commit/d0e0ed6b60f4de6d2b2551cad2fc7a553efe0274
+
+ # Check regex against older RabbitMQ version status output
+ old_server_version = re.search(r'\{rabbit,"RabbitMQ","(.+)"\}', res)
+ # Check regex against newer RabbitMQ version status output
+ server_version = re.search(r"RabbitMQ version:\s*(.+)", res)
+
+ if server_version is None and old_server_version is None:
+ raise ValueError
+
+ if old_server_version:
+ server_version = old_server_version
+ server_version = server_version.group(1).split("-")[0]
+ version = [int(i) for i in server_version.split(".")]
+
+ except ValueError:
+ version = (0, 0, 0)
+ if len(version) < 3:
+ version = (0, 0, 0)
+
+ # rabbitmq introduced a native api to check a username and password in version 3.5.7.
+ if tuple(version) >= (3, 5, 7):
+ if salt.utils.is_windows():
+ # On Windows, if the password contains a special character
+ # such as '|', normal execution will fail. For example:
+ # cmd: rabbitmq.add_user abc "asdf|def"
+ # stderr: 'def' is not recognized as an internal or external
+ # command,\r\noperable program or batch file.
+ # Work around this by using a shell and a quoted command.
+ python_shell = True
+ cmd = '"{0}" authenticate_user "{1}" "{2}"'.format(
+ RABBITMQCTL, name, password
+ )
+ else:
+ python_shell = False
+ cmd = [RABBITMQCTL, 'authenticate_user', name, password]
+
+ res = __salt__['cmd.run_all'](
+ cmd,
+ reset_system_locale=False,
+ runas=runas,
+ output_loglevel='quiet',
+ python_shell=python_shell)
+
+ if res['retcode'] != 0 or res['stderr']:
+ return False
+ return True
+
+ cmd = ('rabbit_auth_backend_internal:check_user_login'
+ '(<<"{0}">>, [{{password, <<"{1}">>}}]).').format(
+ name.replace('"', '\\"'),
+ password.replace('"', '\\"'))
+
+ res = __salt__['cmd.run_all'](
+ [RABBITMQCTL, 'eval', cmd],
+ reset_system_locale=False,
+ runas=runas,
+ output_loglevel='quiet',
+ python_shell=False)
+ msg = 'password-check'
+
+ _response = _format_response(res, msg)
+ _key = _response.keys()[0]
+
+ if 'invalid credentials' in _response[_key]:
+ return False
+
+ return True
diff --git a/_states/rabbitmq_user_custom.py b/_states/rabbitmq_user_custom.py
new file mode 100644
index 0000000..49b6622
--- /dev/null
+++ b/_states/rabbitmq_user_custom.py
@@ -0,0 +1,236 @@
+# -*- coding: utf-8 -*-
+'''
+Manage RabbitMQ Users
+=====================
+
+Example:
+
+.. code-block:: yaml
+
+ rabbit_user:
+ rabbitmq_user.present:
+ - password: password
+ - force: True
+ - tags:
+ - monitoring
+ - user
+ - perms:
+ - '/':
+ - '.*'
+ - '.*'
+ - '.*'
+ - runas: rabbitmq
+'''
+
+# This file is copy of original /usr/lib/python2.7/dist-packages/salt/states/rabbitmq_user.py
+# with fix applied for PROD-35125 - rabbitmq module incompatible with rabbitmq-server 3.8+
+# It change module for check_password function.
+# Now it uses rabbitmq_custom module instead of rabbitmq for check_password function.
+
+# Import python libs
+from __future__ import absolute_import
+import logging
+
+# Import salt libs
+import salt.utils
+import salt.ext.six as six
+from salt.exceptions import CommandExecutionError
+
+log = logging.getLogger(__name__)
+
+
+def __virtual__():
+ '''
+ Only load if RabbitMQ is installed.
+ '''
+ return salt.utils.which('rabbitmqctl') is not None
+
+
+def _check_perms_changes(name, newperms, runas=None, existing=None):
+ '''
+ Check whether Rabbitmq user's permissions need to be changed.
+ '''
+ if not newperms:
+ return False
+
+ if existing is None:
+ try:
+ existing = __salt__['rabbitmq.list_user_permissions'](name, runas=runas)
+ except CommandExecutionError as err:
+ log.error('Error: {0}'.format(err))
+ return False
+
+ perm_need_change = False
+ for vhost_perms in newperms:
+ for vhost, perms in six.iteritems(vhost_perms):
+ if vhost in existing:
+ existing_vhost = existing[vhost]
+ if perms != existing_vhost:
+ # This checks for setting permissions to nothing in the state,
+ # when previous state runs have already set permissions to
+ # nothing. We don't want to report a change in this case.
+ if existing_vhost == '' and perms == ['', '', '']:
+ continue
+ perm_need_change = True
+ else:
+ perm_need_change = True
+
+ return perm_need_change
+
+
+def _get_current_tags(name, runas=None):
+ '''
+ Whether Rabbitmq user's tags need to be changed
+ '''
+ try:
+ return list(__salt__['rabbitmq.list_users'](runas=runas)[name])
+ except CommandExecutionError as err:
+ log.error('Error: {0}'.format(err))
+ return []
+
+
+def present(name,
+ password=None,
+ force=False,
+ tags=None,
+ perms=(),
+ runas=None):
+ '''
+ Ensure the RabbitMQ user exists.
+
+ name
+ User name
+ password
+ User's password, if one needs to be set
+ force
+ If user exists, forcibly change the password
+ tags
+ Optional list of tags for the user
+ perms
+ A list of dicts with vhost keys and 3-tuple values
+ runas
+ Name of the user to run the command
+ '''
+ ret = {'name': name, 'result': False, 'comment': '', 'changes': {}}
+
+ try:
+ user = __salt__['rabbitmq.user_exists'](name, runas=runas)
+ except CommandExecutionError as err:
+ ret['comment'] = 'Error: {0}'.format(err)
+ return ret
+
+ passwd_reqs_update = False
+ if user and password is not None:
+ try:
+ # Fix for PROD-35125
+ # Use rabbitmq_custom module instead of rabbitmq for check_password function
+ if not __salt__['rabbitmq_custom.check_password'](name,
+ password, runas=runas):
+ passwd_reqs_update = True
+ log.debug('RabbitMQ user %s password update required', name)
+ except CommandExecutionError as err:
+ ret['comment'] = 'Error: {0}'.format(err)
+ return ret
+
+ if user and not any((force, perms, tags, passwd_reqs_update)):
+ log.debug(('RabbitMQ user \'%s\' exists, password is up to'
+ ' date and force is not set.'), name)
+ ret['comment'] = 'User \'{0}\' is already present.'.format(name)
+ ret['result'] = True
+ return ret
+
+ if not user:
+ ret['changes'].update({'user':
+ {'old': '',
+ 'new': name}})
+ if __opts__['test']:
+ ret['result'] = None
+ ret['comment'] = 'User \'{0}\' is set to be created.'.format(name)
+ return ret
+
+ log.debug(
+ 'RabbitMQ user \'{0}\' doesn\'t exist - Creating.'.format(name))
+ try:
+ __salt__['rabbitmq.add_user'](name, password, runas=runas)
+ except CommandExecutionError as err:
+ ret['comment'] = 'Error: {0}'.format(err)
+ return ret
+ else:
+ log.debug('RabbitMQ user \'{0}\' exists'.format(name))
+ if force or passwd_reqs_update:
+ if password is not None:
+ if not __opts__['test']:
+ try:
+ __salt__['rabbitmq.change_password'](name, password, runas=runas)
+ except CommandExecutionError as err:
+ ret['comment'] = 'Error: {0}'.format(err)
+ return ret
+ ret['changes'].update({'password':
+ {'old': '',
+ 'new': 'Set password.'}})
+ else:
+ if not __opts__['test']:
+ log.debug('Password for {0} is not set - Clearing password.'.format(name))
+ try:
+ __salt__['rabbitmq.clear_password'](name, runas=runas)
+ except CommandExecutionError as err:
+ ret['comment'] = 'Error: {0}'.format(err)
+ return ret
+ ret['changes'].update({'password':
+ {'old': 'Removed password.',
+ 'new': ''}})
+
+ if tags is not None:
+ current_tags = _get_current_tags(name, runas=runas)
+ if isinstance(tags, str):
+ tags = tags.split()
+ # Diff the tags sets. Symmetric difference operator ^ will give us
+ # any element in one set, but not both
+ if set(tags) ^ set(current_tags):
+ if not __opts__['test']:
+ try:
+ __salt__['rabbitmq.set_user_tags'](name, tags, runas=runas)
+ except CommandExecutionError as err:
+ ret['comment'] = 'Error: {0}'.format(err)
+ return ret
+ ret['changes'].update({'tags':
+ {'old': current_tags,
+ 'new': tags}})
+ try:
+ existing_perms = __salt__['rabbitmq.list_user_permissions'](name, runas=runas)
+ except CommandExecutionError as err:
+ ret['comment'] = 'Error: {0}'.format(err)
+ return ret
+
+ if _check_perms_changes(name, perms, runas=runas, existing=existing_perms):
+ for vhost_perm in perms:
+ for vhost, perm in six.iteritems(vhost_perm):
+ if not __opts__['test']:
+ try:
+ __salt__['rabbitmq.set_permissions'](
+ vhost, name, perm[0], perm[1], perm[2], runas=runas
+ )
+ except CommandExecutionError as err:
+ ret['comment'] = 'Error: {0}'.format(err)
+ return ret
+ new_perms = {vhost: perm}
+ if existing_perms != new_perms:
+ if ret['changes'].get('perms') is None:
+ ret['changes'].update({'perms':
+ {'old': {},
+ 'new': {}}})
+ ret['changes']['perms']['old'].update(existing_perms)
+ ret['changes']['perms']['new'].update(new_perms)
+
+ ret['result'] = True
+ if ret['changes'] == {}:
+ ret['comment'] = '\'{0}\' is already in the desired state.'.format(name)
+ return ret
+
+ if __opts__['test']:
+ ret['result'] = None
+ ret['comment'] = 'Configuration for \'{0}\' will change.'.format(name)
+ return ret
+
+ ret['comment'] = '\'{0}\' was configured.'.format(name)
+ return ret
diff --git a/rabbitmq/server/user.sls b/rabbitmq/server/user.sls
index 600e3e7..9f71963 100644
--- a/rabbitmq/server/user.sls
+++ b/rabbitmq/server/user.sls
@@ -7,7 +7,7 @@
{%- if server.admin is defined %}
rabbit_user_admin_present:
- rabbitmq_user.present:
+ rabbitmq_user_custom.present:
- name: {{ server.admin.name }}
- password: {{ server.admin.password }}
- force: True
diff --git a/rabbitmq/server/vhost.sls b/rabbitmq/server/vhost.sls
index f53019c..8e71f3e 100644
--- a/rabbitmq/server/vhost.sls
+++ b/rabbitmq/server/vhost.sls
@@ -16,7 +16,7 @@
{%- endif %}
rabbitmq_user_{{ host.user }}:
- rabbitmq_user.present:
+ rabbitmq_user_custom.present:
- name: {{ host.user }}
- password: {{ host.password }}
- force: true
diff --git a/rabbitmq/upgrade/verify/_service.sls b/rabbitmq/upgrade/verify/_service.sls
index e212b2a..9bf9221 100644
--- a/rabbitmq/upgrade/verify/_service.sls
+++ b/rabbitmq/upgrade/verify/_service.sls
@@ -6,10 +6,11 @@
{%- if server.get('enabled') %}
{% set host_id = salt['network.get_hostname']() %}
+{% set rmq_version = salt['pkg.version']('rabbitmq-server') %}
rabbitmq_status:
cmd.run:
- - name: rabbitmqctl cluster_status |grep -w running_nodes |grep -w {{ host_id }}
+ - name: rabbitmqctl cluster_status{%- if salt['pkg.version_cmp'](rmq_version,'3.8') >= 0 %} --formatter erlang {%- endif %}|grep -w running_nodes |grep -w {{ host_id }}
{%- if grains.get('noservices') %}
- onlyif: /bin/false
{%- endif %}