]> git.apps.os.sepia.ceph.com Git - ceph-ansible.git/commitdiff
Use upstream config_template collection
authorDmitriy Rabotyagov <noonedeadpunk@ya.ru>
Thu, 13 Jan 2022 15:57:50 +0000 (17:57 +0200)
committerGuillaume Abrioux <gabrioux@redhat.com>
Tue, 18 Jan 2022 19:22:10 +0000 (20:22 +0100)
In order to reduce need of module
internal maintenance and to join forces on plugin development,
it's proposed to switch to using upstream version of
config_template module.

As it's shipped as collection, it's installation for end-users
is trivial and aligns with general approach of shipping extra modules.

Signed-off-by: Dmitriy Rabotyagov <noonedeadpunk@ya.ru>
20 files changed:
.github/workflows/ansible-lint.yml
.gitignore
docs/source/index.rst
infrastructure-playbooks/take-over-existing-cluster.yml
library/config_template [deleted file]
plugins/actions/config_template.py [deleted file]
requirements.yml [new file with mode: 0644]
roles/ceph-common/plugins/actions/config_template.py [deleted file]
roles/ceph-config/tasks/main.yml
roles/ceph-grafana/tasks/configure_grafana.yml
roles/ceph-iscsi-gw/tasks/common.yml
roles/ceph-mds/tasks/non_containerized.yml
roles/ceph-mgr/tasks/start_mgr.yml
roles/ceph-mon/tasks/start_monitor.yml
roles/ceph-nfs/tasks/start_nfs.yml
roles/ceph-osd/tasks/start_osds.yml
roles/ceph-prometheus/tasks/main.yml
roles/ceph-rbd-mirror/tasks/start_rbd_mirror.yml
roles/ceph-rgw/tasks/start_radosgw.yml
tox.ini

index 9dc058f4e94adf13847c50a97f6cff2115be5ca0..d4994387ed22f627429dd326c9ecbda9f5052afe 100644 (file)
@@ -11,6 +11,7 @@ jobs:
           python-version: '3.8'
           architecture: x64
       - run: pip install -r <(grep ansible tests/requirements.txt) ansible-lint==4.3.7 'rich>=9.5.1,<11.0.0' netaddr
+      - run: ansible-galaxy install -r requirements.yml
       - run: ansible-lint -x 106,204,205,208 -v --force-color ./roles/*/ ./infrastructure-playbooks/*.yml site-container.yml.sample site-container.yml.sample dashboard.yml
       - run: ansible-playbook -i ./tests/functional/all_daemons/hosts site.yml.sample --syntax-check --list-tasks -vv
       - run: ansible-playbook -i ./tests/functional/all_daemons/hosts site-container.yml.sample --syntax-check --list-tasks -vv
index 583556ecc8b880146dea9b07c2ec0a6210aa1b72..23d4c25bb5602959821effe94fc695c76b5a7d8c 100644 (file)
@@ -20,3 +20,4 @@ ceph-ansible.spec
 !.travis.yml
 !.mergify.yml
 !raw_install_python.yml
+!requirements.yml
index 9ba499020fcd8a2558f69cc7653dba18c1ad1ef5..083432de4380d3d9613381f2e08be0ad36edf5c7 100644 (file)
@@ -63,6 +63,16 @@ You can acquire Ansible on Ubuntu by using the `Ansible PPA <https://launchpad.n
    $ sudo apt update
    $ sudo apt install ansible
 
+Ansible collections
+-------------------
+
+In order to install third-party collections that are required for ceph-ansible,
+please run:
+
+.. code-block:: console
+
+   $ ansible-galaxy install -r requirements.yml
+
 
 Releases
 ========
index ddd6b2fd1644e5b486683b7ae1718970010b236f..9c913a15e59a5f2ae5f3af1ec7cbe042009c666a 100644 (file)
@@ -58,8 +58,7 @@
         mode: "{{ ceph_conf_stat.stat.mode }}"
 
     - name: generate ceph configuration file
-      action: config_template
-      args:
+      openstack.config_template.config_template:
         src: "roles/ceph-config/templates/ceph.conf.j2"
         dest: "/etc/ceph/{{ cluster_name.stdout }}.conf"
         owner: "{{ ceph_conf_stat.stat.pw_name }}"
diff --git a/library/config_template b/library/config_template
deleted file mode 100644 (file)
index 0a4bc31..0000000
+++ /dev/null
@@ -1,66 +0,0 @@
-# this is a virtual module that is entirely implemented server side
-
-DOCUMENTATION = """
----
-module: config_template
-version_added: 1.9.2
-short_description: Renders template files providing a create/update override interface
-description:
-  - The module contains the template functionality with the ability to override items
-    in config, in transit, though the use of an simple dictionary without having to
-    write out various temp files on target machines. The module renders all of the
-    potential jinja a user could provide in both the template file and in the override
-    dictionary which is ideal for deployers whom may have lots of different configs
-    using a similar code base.
-  - The module is an extension of the **copy** module and all of attributes that can be
-    set there are available to be set here.
-options:
-  src:
-    description:
-      - Path of a Jinja2 formatted template on the local server. This can be a relative
-        or absolute path.
-    required: true
-    default: null
-  dest:
-    description:
-      - Location to render the template to on the remote machine.
-    required: true
-    default: null
-  config_overrides:
-    description:
-      - A dictionary used to update or override items within a configuration template.
-        The dictionary data structure may be nested. If the target config file is an ini
-        file the nested keys in the ``config_overrides`` will be used as section
-        headers.
-  config_type:
-    description:
-      - A string value describing the target config type.
-    choices:
-      - ini
-      - json
-      - yaml
-author: Kevin Carter
-"""
-
-EXAMPLES = """
-  - name: run config template ini
-    config_template:
-      src: templates/test.ini.j2
-      dest: /tmp/test.ini
-      config_overrides: {}
-      config_type: ini
-
-  - name: run config template json
-    config_template:
-      src: templates/test.json.j2
-      dest: /tmp/test.json
-      config_overrides: {}
-      config_type: json
-
-  - name: run config template yaml
-    config_template:
-      src: templates/test.yaml.j2
-      dest: /tmp/test.yaml
-      config_overrides: {}
-      config_type: yaml
-"""
diff --git a/plugins/actions/config_template.py b/plugins/actions/config_template.py
deleted file mode 100644 (file)
index b7890d2..0000000
+++ /dev/null
@@ -1,844 +0,0 @@
-# (c) 2015, Kevin Carter <kevin.carter@rackspace.com>
-#
-# This file is part of Ansible
-#
-# Ansible is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# Ansible is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with Ansible.  If not, see <http://www.gnu.org/licenses/>.
-
-from __future__ import (absolute_import, division, print_function)
-
-try:
-    import ConfigParser
-except ImportError:
-    import configparser as ConfigParser
-import datetime
-try:
-    from StringIO import StringIO
-except ImportError:
-    from io import StringIO
-import base64
-import json
-import os
-import pwd
-import re
-import six
-import time
-import yaml
-import tempfile as tmpfilelib
-
-from ansible.plugins.action import ActionBase
-from ansible.module_utils._text import to_bytes, to_text
-from ansible import constants as C
-from ansible import errors
-from ansible.parsing.yaml.dumper import AnsibleDumper
-from distutils.version import LooseVersion
-from ansible import __version__ as __ansible_version__
-
-__metaclass__ = type
-
-
-CONFIG_TYPES = {
-    'ini': 'return_config_overrides_ini',
-    'json': 'return_config_overrides_json',
-    'yaml': 'return_config_overrides_yaml'
-}
-
-
-class IDumper(AnsibleDumper):
-    def increase_indent(self, flow=False, indentless=False):
-        return super(IDumper, self).increase_indent(flow, False)
-
-
-class MultiKeyDict(dict):
-    """Dictionary class which supports duplicate keys.
-    This class allows for an item to be added into a standard python dictionary
-    however if a key is created more than once the dictionary will convert the
-    singular value to a python tuple. This tuple type forces all values to be a
-    string.
-    Example Usage:
-    >>> z = MultiKeyDict()
-    >>> z['a'] = 1
-    >>> z['b'] = ['a', 'b', 'c']
-    >>> z['c'] = {'a': 1}
-    >>> print(z)
-    ... {'a': 1, 'b': ['a', 'b', 'c'], 'c': {'a': 1}}
-    >>> z['a'] = 2
-    >>> print(z)
-    ... {'a': tuple(['1', '2']), 'c': {'a': 1}, 'b': ['a', 'b', 'c']}
-    """
-
-    def __setitem__(self, key, value):
-        if key in self:
-            if isinstance(self[key], tuple):
-                items = self[key]
-                if str(value) not in items:
-                    items += tuple([str(value)])
-                    super(MultiKeyDict, self).__setitem__(key, items)
-            else:
-                if str(self[key]) != str(value):
-                    items = tuple([str(self[key]), str(value)])
-                    super(MultiKeyDict, self).__setitem__(key, items)
-        else:
-            return dict.__setitem__(self, key, value)
-
-
-class ConfigTemplateParser(ConfigParser.RawConfigParser):
-    """ConfigParser which supports multi key value.
-    The parser will use keys with multiple variables in a set as a multiple
-    key value within a configuration file.
-    Default Configuration file:
-    [DEFAULT]
-    things =
-        url1
-        url2
-        url3
-    other = 1,2,3
-    [section1]
-    key = var1
-    key = var2
-    key = var3
-    Example Usage:
-    >>> cp = ConfigTemplateParser(dict_type=MultiKeyDict)
-    >>> cp.read('/tmp/test.ini')
-    ... ['/tmp/test.ini']
-    >>> cp.get('DEFAULT', 'things')
-    ... \nurl1\nurl2\nurl3
-    >>> cp.get('DEFAULT', 'other')
-    ... '1,2,3'
-    >>> cp.set('DEFAULT', 'key1', 'var1')
-    >>> cp.get('DEFAULT', 'key1')
-    ... 'var1'
-    >>> cp.get('section1', 'key')
-    ... {'var1', 'var2', 'var3'}
-    >>> cp.set('section1', 'key', 'var4')
-    >>> cp.get('section1', 'key')
-    ... {'var1', 'var2', 'var3', 'var4'}
-    >>> with open('/tmp/test2.ini', 'w') as f:
-    ...     cp.write(f)
-    Output file:
-    [DEFAULT]
-    things =
-        url1
-        url2
-        url3
-    key1 = var1
-    other = 1,2,3
-    [section1]
-    key = var4
-    key = var1
-    key = var3
-    key = var2
-    """
-
-    def __init__(self, *args, **kwargs):
-        self._comments = {}
-        self.ignore_none_type = bool(kwargs.pop('ignore_none_type', True))
-        self.default_section = str(kwargs.pop('default_section', 'DEFAULT'))
-        ConfigParser.RawConfigParser.__init__(self, *args, **kwargs)
-
-    def _write(self, fp, section, key, item, entry):
-        if section:
-            # If we are not ignoring a none type value, then print out
-            # the option name only if the value type is None.
-            if not self.ignore_none_type and item is None:
-                fp.write(key + '\n')
-            elif (item is not None) or (self._optcre == self.OPTCRE):
-                fp.write(entry)
-        else:
-            fp.write(entry)
-
-    def _write_check(self, fp, key, value, section=False):
-        if isinstance(value, (tuple, set)):
-            for item in value:
-                item = str(item).replace('\n', '\n\t')
-                entry = "%s = %s\n" % (key, item)
-                self._write(fp, section, key, item, entry)
-        else:
-            if isinstance(value, list):
-                _value = [str(i.replace('\n', '\n\t')) for i in value]
-                entry = '%s = %s\n' % (key, ','.join(_value))
-            else:
-                entry = '%s = %s\n' % (key, str(value).replace('\n', '\n\t'))
-            self._write(fp, section, key, value, entry)
-
-    def write(self, fp):
-        def _do_write(section_name, section, section_bool=False):
-            _write_comments(section_name)
-            fp.write("[%s]\n" % section_name)
-            for key, value in sorted(section.items()):
-                _write_comments(section_name, optname=key)
-                self._write_check(fp, key=key, value=value,
-                                  section=section_bool)
-
-            fp.write("\n")
-
-        def _write_comments(section, optname=None):
-            comsect = self._comments.get(section, {})
-            if optname in comsect:
-                fp.write(''.join(comsect[optname]))
-
-        if self.default_section != 'DEFAULT' and self._sections.get(
-                self.default_section, False):
-            _do_write(self.default_section,
-                      self._sections[self.default_section],
-                      section_bool=True)
-            self._sections.pop(self.default_section)
-
-        if self._defaults:
-            _do_write('DEFAULT', self._defaults)
-
-        for section in sorted(self._sections):
-            _do_write(section, self._sections[section], section_bool=True)
-
-    def _read(self, fp, fpname):
-        comments = []
-        cursect = None
-        optname = None
-        lineno = 0
-        e = None
-        while True:
-            line = fp.readline()
-            if not line:
-                break
-            lineno += 1
-            if line.strip() == '':
-                if comments:
-                    comments.append('')
-                continue
-
-            if line.lstrip()[0] in '#;':
-                comments.append(line.lstrip())
-                continue
-
-            if line.split(None, 1)[0].lower() == 'rem' and line[0] in "rR":
-                continue
-            if line[0].isspace() and cursect is not None and optname:
-                value = line.strip()
-                if value:
-                    try:
-                        if isinstance(cursect[optname], (tuple, set)):
-                            _temp_item = list(cursect[optname])
-                            del cursect[optname]
-                            cursect[optname] = _temp_item
-                        elif isinstance(cursect[optname], six.text_type):
-                            _temp_item = [cursect[optname]]
-                            del cursect[optname]
-                            cursect[optname] = _temp_item
-                    except NameError:
-                        if isinstance(cursect[optname], (bytes, str)):
-                            _temp_item = [cursect[optname]]
-                            del cursect[optname]
-                            cursect[optname] = _temp_item
-                    cursect[optname].append(value)
-            else:
-                mo = self.SECTCRE.match(line)
-                if mo:
-                    sectname = mo.group('header')
-                    if sectname in self._sections:
-                        cursect = self._sections[sectname]
-                    elif sectname == 'DEFAULT':
-                        cursect = self._defaults
-                    else:
-                        cursect = self._dict()
-                        self._sections[sectname] = cursect
-                    optname = None
-
-                    comsect = self._comments.setdefault(sectname, {})
-                    if comments:
-                        # NOTE(flaper87): Using none as the key for
-                        # section level comments
-                        comsect[None] = comments
-                        comments = []
-                elif cursect is None:
-                    raise ConfigParser.MissingSectionHeaderError(
-                        fpname,
-                        lineno,
-                        line
-                    )
-                else:
-                    mo = self._optcre.match(line)
-                    if mo:
-                        optname, vi, optval = mo.group('option', 'vi', 'value')
-                        optname = self.optionxform(optname.rstrip())
-                        if optval is not None:
-                            if vi in ('=', ':') and ';' in optval:
-                                pos = optval.find(';')
-                                if pos != -1 and optval[pos - 1].isspace():
-                                    optval = optval[:pos]
-                            optval = optval.strip()
-                            if optval == '""':
-                                optval = ''
-                        cursect[optname] = optval
-                        if comments:
-                            comsect[optname] = comments
-                            comments = []
-                    else:
-                        if not e:
-                            e = ConfigParser.ParsingError(fpname)
-                        e.append(lineno, repr(line))
-        if e:
-            raise e
-        all_sections = [self._defaults]
-        all_sections.extend(self._sections.values())
-        for options in all_sections:
-            for name, val in options.items():
-                if isinstance(val, list):
-                    _temp_item = '\n'.join(val)
-                    del options[name]
-                    options[name] = _temp_item
-
-
-class DictCompare(object):
-    """
-    Calculate the difference between two dictionaries.
-
-    Example Usage:
-    >>> base_dict = {'test1': 'val1', 'test2': 'val2', 'test3': 'val3'}
-    >>> new_dict = {'test1': 'val2', 'test3': 'val3', 'test4': 'val3'}
-    >>> dc = DictCompare(base_dict, new_dict)
-    >>> dc.added()
-    ... ['test4']
-    >>> dc.removed()
-    ... ['test2']
-    >>> dc.changed()
-    ... ['test1']
-    >>> dc.get_changes()
-    ... {'added':
-    ...     {'test4': 'val3'},
-    ...  'removed':
-    ...     {'test2': 'val2'},
-    ...  'changed':
-    ...     {'test1': {'current_val': 'vol1', 'new_val': 'val2'}
-    ... }
-    """
-    def __init__(self, base_dict, new_dict):
-        self.new_dict, self.base_dict = new_dict, base_dict
-        self.base_items, self.new_items = set(
-            self.base_dict.keys()), set(self.new_dict.keys())
-        self.intersect = self.new_items.intersection(self.base_items)
-
-    def added(self):
-        return self.new_items - self.intersect
-
-    def removed(self):
-        return self.base_items - self.intersect
-
-    def changed(self):
-        return set(
-            x for x in self.intersect if self.base_dict[x] != self.new_dict[x])
-
-    def get_changes(self):
-        """Returns dict of differences between 2 dicts and bool indicating if
-        there are differences
-
-        :param base_dict: ``dict``
-        :param new_dict: ``dict``
-        :returns: ``dict``, ``bool``
-        """
-        changed = False
-        mods = {'added': {}, 'removed': {}, 'changed': {}}
-
-        for s in self.changed():
-            changed = True
-            if type(self.base_dict[s]) is not dict:
-                mods['changed'] = {
-                    s: {'current_val': self.base_dict[s],
-                        'new_val': self.new_dict[s]}}
-                continue
-
-            diff = DictCompare(self.base_dict[s], self.new_dict[s])
-            for a in diff.added():
-                if s not in mods['added']:
-                    mods['added'][s] = {a: self.new_dict[s][a]}
-                else:
-                    mods['added'][s][a] = self.new_dict[s][a]
-
-            for r in diff.removed():
-                if s not in mods['removed']:
-                    mods['removed'][s] = {r: self.base_dict[s][r]}
-                else:
-                    mods['removed'][s][r] = self.base_dict[s][r]
-
-            for c in diff.changed():
-                if s not in mods['changed']:
-                    mods['changed'][s] = {
-                        c: {'current_val': self.base_dict[s][c],
-                            'new_val': self.new_dict[s][c]}}
-                else:
-                    mods['changed'][s][c] = {
-                        'current_val': self.base_dict[s][c],
-                        'new_val': self.new_dict[s][c]}
-
-        for s in self.added():
-            changed = True
-            mods['added'][s] = self.new_dict[s]
-
-        for s in self.removed():
-            changed = True
-            mods['removed'][s] = self.base_dict[s]
-
-        return mods, changed
-
-
-class ActionModule(ActionBase):
-    TRANSFERS_FILES = True
-
-    def return_config_overrides_ini(self,
-                                    config_overrides,
-                                    resultant,
-                                    list_extend=True,
-                                    ignore_none_type=True,
-                                    default_section='DEFAULT'):
-        """Returns string value from a modified config file and dict of
-        merged config
-
-        :param config_overrides: ``dict``
-        :param resultant: ``str`` || ``unicode``
-        :returns: ``str``, ``dict``
-        """
-        # If there is an exception loading the RawConfigParser The config obj
-        #  is loaded again without the extra option. This is being done to
-        #  support older python.
-        try:
-            config = ConfigTemplateParser(
-                allow_no_value=True,
-                dict_type=MultiKeyDict,
-                ignore_none_type=ignore_none_type,
-                default_section=default_section
-            )
-            config.optionxform = str
-        except Exception:
-            config = ConfigTemplateParser(dict_type=MultiKeyDict)
-
-        config_object = StringIO(resultant)
-        config.readfp(config_object)
-
-        for section, items in config_overrides.items():
-            # If the items value is not a dictionary it is assumed that the
-            #  value is a default item for this config type.
-            if not isinstance(items, dict):
-                if isinstance(items, list):
-                    items = ','.join(to_text(i) for i in items)
-                self._option_write(
-                    config,
-                    'DEFAULT',
-                    section,
-                    items
-                )
-            else:
-                # Attempt to add a section to the config file passing if
-                #  an error is raised that is related to the section
-                #  already existing.
-                try:
-                    config.add_section(section)
-                except (ConfigParser.DuplicateSectionError, ValueError):
-                    pass
-                for key, value in items.items():
-                    try:
-                        self._option_write(config, section, key, value)
-                    except ConfigParser.NoSectionError as exp:
-                        error_msg = str(exp)
-                        error_msg += (
-                            ' Try being more explicit with your override'
-                            'data. Sections are case sensitive.'
-                        )
-                        raise errors.AnsibleModuleError(error_msg)
-
-        config_object.close()
-
-        config_dict_new = {}
-        config_defaults = config.defaults()
-        for s in config.sections():
-            config_dict_new[s] = {}
-            for k, v in config.items(s):
-                if k not in config_defaults or config_defaults[k] != v:
-                    config_dict_new[s][k] = v
-                else:
-                    if default_section in config_dict_new:
-                        config_dict_new[default_section][k] = v
-                    else:
-                        config_dict_new[default_section] = {k: v}
-
-        resultant_stringio = StringIO()
-        try:
-            config.write(resultant_stringio)
-            return resultant_stringio.getvalue(), config_dict_new
-        finally:
-            resultant_stringio.close()
-
-    @staticmethod
-    def _option_write(config, section, key, value):
-        config.remove_option(str(section), str(key))
-        try:
-            if not any(list(value.values())):
-                value = tuple(value.keys())
-        except AttributeError:
-            pass
-        if isinstance(value, (tuple, set)):
-            config.set(str(section), str(key), value)
-        elif isinstance(value, set):
-            config.set(str(section), str(key), value)
-        elif isinstance(value, list):
-            config.set(str(section), str(key), ','.join(str(i) for i in value))
-        else:
-            config.set(str(section), str(key), str(value))
-
-    def return_config_overrides_json(self,
-                                     config_overrides,
-                                     resultant,
-                                     list_extend=True,
-                                     ignore_none_type=True,
-                                     default_section='DEFAULT'):
-        """Returns config json and dict of merged config
-
-        Its important to note that file ordering will not be preserved as the
-        information within the json file will be sorted by keys.
-
-        :param config_overrides: ``dict``
-        :param resultant: ``str`` || ``unicode``
-        :returns: ``str``, ``dict``
-        """
-        original_resultant = json.loads(resultant)
-        merged_resultant = self._merge_dict(
-            base_items=original_resultant,
-            new_items=config_overrides,
-            list_extend=list_extend
-        )
-        return json.dumps(
-            merged_resultant,
-            indent=4,
-            sort_keys=True
-        ), merged_resultant
-
-    def return_config_overrides_yaml(self,
-                                     config_overrides,
-                                     resultant,
-                                     list_extend=True,
-                                     ignore_none_type=True,
-                                     default_section='DEFAULT'):
-        """Return config yaml and dict of merged config
-
-        :param config_overrides: ``dict``
-        :param resultant: ``str`` || ``unicode``
-        :returns: ``str``, ``dict``
-        """
-        original_resultant = yaml.safe_load(resultant)
-        merged_resultant = self._merge_dict(
-            base_items=original_resultant,
-            new_items=config_overrides,
-            list_extend=list_extend
-        )
-        return yaml.dump(
-            merged_resultant,
-            Dumper=IDumper,
-            default_flow_style=False,
-            width=1000,
-        ), merged_resultant
-
-    def _merge_dict(self, base_items, new_items, list_extend=True):
-        """Recursively merge new_items into base_items.
-
-        :param base_items: ``dict``
-        :param new_items: ``dict``
-        :returns: ``dict``
-        """
-        for key, value in new_items.items():
-            if isinstance(value, dict):
-                base_items[key] = self._merge_dict(
-                    base_items=base_items.get(key, {}),
-                    new_items=value,
-                    list_extend=list_extend
-                )
-            elif (not isinstance(value, int) and
-                  (',' in value or '\n' in value)):
-                base_items[key] = re.split(',|\n', value)
-                base_items[key] = [i.strip() for i in base_items[key] if i]
-            elif isinstance(value, list):
-                if isinstance(base_items.get(key), list) and list_extend:
-                    base_items[key].extend(value)
-                else:
-                    base_items[key] = value
-            elif isinstance(value, (tuple, set)):
-                if isinstance(base_items.get(key), tuple) and list_extend:
-                    base_items[key] += tuple(value)
-                elif isinstance(base_items.get(key), list) and list_extend:
-                    base_items[key].extend(list(value))
-                else:
-                    base_items[key] = value
-            else:
-                base_items[key] = new_items[key]
-        return base_items
-
-    def _load_options_and_status(self, task_vars):
-        """Return options and status from module load."""
-
-        config_type = self._task.args.get('config_type')
-        if config_type not in ['ini', 'yaml', 'json']:
-            return False, dict(
-                failed=True,
-                msg="No valid [ config_type ] was provided. Valid options are"
-                    " ini, yaml, or json."
-            )
-
-        # Access to protected method is unavoidable in Ansible
-        searchpath = [self._loader._basedir]
-
-        if self._task._role:
-            file_path = self._task._role._role_path
-            searchpath.insert(1, C.DEFAULT_ROLES_PATH)
-            searchpath.insert(1, self._task._role._role_path)
-        else:
-            file_path = self._loader.get_basedir()
-
-        user_source = self._task.args.get('src')
-        # (alextricity25) It's possible that the user could pass in a datatype
-        # and not always a string. In this case we don't want the datatype
-        # python representation to be printed out to the file, but rather we
-        # want the serialized version.
-        _user_content = self._task.args.get('content')
-
-        # If the data type of the content input is a dictionary, it's
-        # converted dumped as json if config_type is 'json'.
-        if isinstance(_user_content, dict):
-            if self._task.args.get('config_type') == 'json':
-                _user_content = json.dumps(_user_content)
-
-        user_content = str(_user_content)
-        if not user_source:
-            if not user_content:
-                return False, dict(
-                    failed=True,
-                    msg="No user [ src ] or [ content ] was provided"
-                )
-            else:
-                tmp_content = None
-                fd, tmp_content = tmpfilelib.mkstemp()
-                try:
-                    with open(tmp_content, 'wb') as f:
-                        f.write(user_content.encode())
-                except Exception as err:
-                    os.remove(tmp_content)
-                    raise Exception(err)
-                self._task.args['src'] = source = tmp_content
-        else:
-            source = self._loader.path_dwim_relative(
-                file_path,
-                'templates',
-                user_source
-            )
-        searchpath.insert(1, os.path.dirname(source))
-
-        _dest = self._task.args.get('dest')
-        list_extend = self._task.args.get('list_extend')
-        if not _dest:
-            return False, dict(
-                failed=True,
-                msg="No [ dest ] was provided"
-            )
-        else:
-            # Expand any user home dir specification
-            user_dest = self._remote_expand_user(_dest)
-            if user_dest.endswith(os.sep):
-                user_dest = os.path.join(user_dest, os.path.basename(source))
-
-        # Get ignore_none_type
-        # In some situations(i.e. my.cnf files), INI files can have valueless
-        # options that don't have a '=' or ':' suffix. In these cases,
-        # ConfigParser gives these options a "None" value. If ignore_none_type
-        # is set to true, these key/value options will be ignored, if it's set
-        # to false, then ConfigTemplateParser will write out only the option
-        # name with out the '=' or ':' suffix. The default is true.
-        ignore_none_type = self._task.args.get('ignore_none_type', True)
-
-        default_section = self._task.args.get('default_section', 'DEFAULT')
-
-        return True, dict(
-            source=source,
-            dest=user_dest,
-            config_overrides=self._task.args.get('config_overrides', {}),
-            config_type=config_type,
-            searchpath=searchpath,
-            list_extend=list_extend,
-            ignore_none_type=ignore_none_type,
-            default_section=default_section
-        )
-
-    def run(self, tmp=None, task_vars=None):
-        """Run the method"""
-
-        try:
-            remote_user = task_vars.get('ansible_user')
-            if not remote_user:
-                remote_user = task_vars.get('ansible_ssh_user')
-            if not remote_user:
-                remote_user = self._play_context.remote_user
-
-            if not tmp:
-                tmp = self._make_tmp_path(remote_user)
-        except TypeError:
-            if not tmp:
-                tmp = self._make_tmp_path()
-
-        _status, _vars = self._load_options_and_status(task_vars=task_vars)
-        if not _status:
-            return _vars
-
-        temp_vars = task_vars.copy()
-        template_host = temp_vars['template_host'] = os.uname()[1]
-        source = temp_vars['template_path'] = _vars['source']
-        temp_vars['template_mtime'] = datetime.datetime.fromtimestamp(
-            os.path.getmtime(source)
-        )
-
-        try:
-            template_uid = temp_vars['template_uid'] = pwd.getpwuid(
-                os.stat(source).st_uid
-            ).pw_name
-        except Exception:
-            template_uid = temp_vars['template_uid'] = os.stat(source).st_uid
-
-        managed_default = C.DEFAULT_MANAGED_STR
-        managed_str = managed_default.format(
-            host=template_host,
-            uid=template_uid,
-            file=to_bytes(source)
-        )
-
-        temp_vars['ansible_managed'] = time.strftime(
-            managed_str,
-            time.localtime(os.path.getmtime(source))
-        )
-        temp_vars['template_fullpath'] = os.path.abspath(source)
-        temp_vars['template_run_date'] = datetime.datetime.now()
-
-        with open(source, 'r') as f:
-            template_data = to_text(f.read())
-
-        self._templar.environment.loader.searchpath = _vars['searchpath']
-        self._templar.set_available_variables(temp_vars)
-        resultant = self._templar.template(
-            template_data,
-            preserve_trailing_newlines=True,
-            escape_backslashes=False,
-            convert_data=False
-        )
-
-        # Access to protected method is unavoidable in Ansible
-        self._templar.set_available_variables(
-            self._templar._available_variables
-        )
-
-        config_dict_base = {}
-        type_merger = getattr(self, CONFIG_TYPES.get(_vars['config_type']))
-        resultant, config_dict_base = type_merger(
-            config_overrides=_vars['config_overrides'],
-            resultant=resultant,
-            list_extend=_vars.get('list_extend', True),
-            ignore_none_type=_vars.get('ignore_none_type', True),
-            default_section=_vars.get('default_section', 'DEFAULT')
-        )
-
-        changed = False
-        if self._play_context.diff:
-            slurpee = self._execute_module(
-                module_name='slurp',
-                module_args=dict(src=_vars['dest']),
-                task_vars=task_vars
-            )
-
-            config_dict_new = {}
-            if 'content' in slurpee:
-                dest_data = base64.b64decode(
-                    slurpee['content']).decode('utf-8')
-                resultant_dest = self._templar.template(
-                    dest_data,
-                    preserve_trailing_newlines=True,
-                    escape_backslashes=False,
-                    convert_data=False
-                )
-                type_merger = getattr(self,
-                                      CONFIG_TYPES.get(_vars['config_type']))
-                resultant_new, config_dict_new = type_merger(
-                    config_overrides={},
-                    resultant=resultant_dest,
-                    list_extend=_vars.get('list_extend', True),
-                    ignore_none_type=_vars.get('ignore_none_type', True),
-                    default_section=_vars.get('default_section', 'DEFAULT')
-                )
-
-            # Compare source+overrides with dest to look for changes and
-            # build diff
-            cmp_dicts = DictCompare(config_dict_new, config_dict_base)
-            mods, changed = cmp_dicts.get_changes()
-
-        # Re-template the resultant object as it may have new data within it
-        #  as provided by an override variable.
-        resultant = self._templar.template(
-            resultant,
-            preserve_trailing_newlines=True,
-            escape_backslashes=False,
-            convert_data=False
-        )
-
-        # run the copy module
-        new_module_args = self._task.args.copy()
-        # Access to protected method is unavoidable in Ansible
-        transferred_data = self._transfer_data(
-            self._connection._shell.join_path(tmp, 'source'),
-            resultant
-        )
-        if LooseVersion(__ansible_version__) < LooseVersion("2.6"):
-            new_module_args.update(
-                dict(
-                    src=transferred_data,
-                    dest=_vars['dest'],
-                    original_basename=os.path.basename(source),
-                    follow=True,
-                ),
-            )
-        else:
-            new_module_args.update(
-                dict(
-                    src=transferred_data,
-                    dest=_vars['dest'],
-                    _original_basename=os.path.basename(source),
-                    follow=True,
-                ),
-            )
-
-        # Remove data types that are not available to the copy module
-        new_module_args.pop('config_overrides', None)
-        new_module_args.pop('config_type', None)
-        new_module_args.pop('list_extend', None)
-        new_module_args.pop('ignore_none_type', None)
-        new_module_args.pop('default_section', None)
-        # Content from config_template is converted to src
-        new_module_args.pop('content', None)
-
-        # Run the copy module
-        rc = self._execute_module(
-            module_name='copy',
-            module_args=new_module_args,
-            task_vars=task_vars
-        )
-        copy_changed = rc.get('changed')
-        if not copy_changed:
-            rc['changed'] = changed
-
-        if self._play_context.diff:
-            rc['diff'] = []
-            rc['diff'].append(
-                {'prepared': json.dumps(mods, indent=4, sort_keys=True)})
-        if self._task.args.get('content'):
-            os.remove(_vars['source'])
-        return rc
diff --git a/requirements.yml b/requirements.yml
new file mode 100644 (file)
index 0000000..9ac7ecf
--- /dev/null
@@ -0,0 +1,6 @@
+---
+# These are Ansible requirements needed to run ceph-ansible master
+collections:
+  - name: https://opendev.org/openstack/ansible-config_template
+    version: 1.2.1
+    type: git
diff --git a/roles/ceph-common/plugins/actions/config_template.py b/roles/ceph-common/plugins/actions/config_template.py
deleted file mode 100644 (file)
index e88488f..0000000
+++ /dev/null
@@ -1,696 +0,0 @@
-# (c) 2015, Kevin Carter <kevin.carter@rackspace.com>
-#
-# This file is part of Ansible
-#
-# Ansible is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# Ansible is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with Ansible.  If not, see <http://www.gnu.org/licenses/>.
-
-from __future__ import (absolute_import, division, print_function)
-
-try:
-    import ConfigParser
-except ImportError:
-    import configparser as ConfigParser
-import datetime
-try:
-    from StringIO import StringIO
-except ImportError:
-    from io import StringIO
-import json
-import os
-import pwd
-import re
-import six
-import time
-import yaml
-import tempfile as tmpfilelib
-
-from ansible.plugins.action import ActionBase
-from ansible.module_utils._text import to_bytes, to_text
-from ansible import constants as C
-from ansible import errors
-from ansible.parsing.yaml.dumper import AnsibleDumper
-from distutils.version import LooseVersion
-from ansible import __version__ as __ansible_version__
-
-__metaclass__ = type
-
-
-CONFIG_TYPES = {
-    'ini': 'return_config_overrides_ini',
-    'json': 'return_config_overrides_json',
-    'yaml': 'return_config_overrides_yaml'
-}
-
-
-class IDumper(AnsibleDumper):
-    def increase_indent(self, flow=False, indentless=False):
-        return super(IDumper, self).increase_indent(flow, False)
-
-
-class MultiKeyDict(dict):
-    """Dictionary class which supports duplicate keys.
-    This class allows for an item to be added into a standard python dictionary
-    however if a key is created more than once the dictionary will convert the
-    singular value to a python tuple. This tuple type forces all values to be a
-    string.
-    Example Usage:
-    >>> z = MultiKeyDict()
-    >>> z['a'] = 1
-    >>> z['b'] = ['a', 'b', 'c']
-    >>> z['c'] = {'a': 1}
-    >>> print(z)
-    ... {'a': 1, 'b': ['a', 'b', 'c'], 'c': {'a': 1}}
-    >>> z['a'] = 2
-    >>> print(z)
-    ... {'a': tuple(['1', '2']), 'c': {'a': 1}, 'b': ['a', 'b', 'c']}
-    """
-
-    def __setitem__(self, key, value):
-        if key in self:
-            if isinstance(self[key], tuple):
-                items = self[key]
-                if str(value) not in items:
-                    items += tuple([str(value)])
-                    super(MultiKeyDict, self).__setitem__(key, items)
-            else:
-                if str(self[key]) != str(value):
-                    items = tuple([str(self[key]), str(value)])
-                    super(MultiKeyDict, self).__setitem__(key, items)
-        else:
-            return dict.__setitem__(self, key, value)
-
-
-class ConfigTemplateParser(ConfigParser.RawConfigParser):
-    """ConfigParser which supports multi key value.
-    The parser will use keys with multiple variables in a set as a multiple
-    key value within a configuration file.
-    Default Configuration file:
-    [DEFAULT]
-    things =
-        url1
-        url2
-        url3
-    other = 1,2,3
-    [section1]
-    key = var1
-    key = var2
-    key = var3
-    Example Usage:
-    >>> cp = ConfigTemplateParser(dict_type=MultiKeyDict)
-    >>> cp.read('/tmp/test.ini')
-    ... ['/tmp/test.ini']
-    >>> cp.get('DEFAULT', 'things')
-    ... \nurl1\nurl2\nurl3
-    >>> cp.get('DEFAULT', 'other')
-    ... '1,2,3'
-    >>> cp.set('DEFAULT', 'key1', 'var1')
-    >>> cp.get('DEFAULT', 'key1')
-    ... 'var1'
-    >>> cp.get('section1', 'key')
-    ... {'var1', 'var2', 'var3'}
-    >>> cp.set('section1', 'key', 'var4')
-    >>> cp.get('section1', 'key')
-    ... {'var1', 'var2', 'var3', 'var4'}
-    >>> with open('/tmp/test2.ini', 'w') as f:
-    ...     cp.write(f)
-    Output file:
-    [DEFAULT]
-    things =
-        url1
-        url2
-        url3
-    key1 = var1
-    other = 1,2,3
-    [section1]
-    key = var4
-    key = var1
-    key = var3
-    key = var2
-    """
-
-    def __init__(self, *args, **kwargs):
-        self._comments = {}
-        self.ignore_none_type = bool(kwargs.pop('ignore_none_type', True))
-        self.default_section = str(kwargs.pop('default_section', 'DEFAULT'))
-        ConfigParser.RawConfigParser.__init__(self, *args, **kwargs)
-
-    def _write(self, fp, section, key, item, entry):
-        if section:
-            # If we are not ignoring a none type value, then print out
-            # the option name only if the value type is None.
-            if not self.ignore_none_type and item is None:
-                fp.write(key + '\n')
-            elif (item is not None) or (self._optcre == self.OPTCRE):
-                fp.write(entry)
-        else:
-            fp.write(entry)
-
-    def _write_check(self, fp, key, value, section=False):
-        if isinstance(value, (tuple, set)):
-            for item in value:
-                item = str(item).replace('\n', '\n\t')
-                entry = "%s = %s\n" % (key, item)
-                self._write(fp, section, key, item, entry)
-        else:
-            if isinstance(value, list):
-                _value = [str(i.replace('\n', '\n\t')) for i in value]
-                entry = '%s = %s\n' % (key, ','.join(_value))
-            else:
-                entry = '%s = %s\n' % (key, str(value).replace('\n', '\n\t'))
-            self._write(fp, section, key, value, entry)
-
-    def write(self, fp):
-        def _do_write(section_name, section, section_bool=False):
-            _write_comments(section_name)
-            fp.write("[%s]\n" % section_name)
-            for key, value in sorted(section.items()):
-                _write_comments(section_name, optname=key)
-                self._write_check(fp, key=key, value=value,
-                                  section=section_bool)
-            else:
-                fp.write("\n")
-
-        def _write_comments(section, optname=None):
-            comsect = self._comments.get(section, {})
-            if optname in comsect:
-                fp.write(''.join(comsect[optname]))
-
-        if self.default_section != 'DEFAULT' and self._sections.get(
-                self.default_section, False):
-            _do_write(self.default_section,
-                      self._sections[self.default_section],
-                      section_bool=True)
-            self._sections.pop(self.default_section)
-
-        if self._defaults:
-            _do_write('DEFAULT', self._defaults)
-
-        for section in sorted(self._sections):
-            _do_write(section, self._sections[section], section_bool=True)
-
-    def _read(self, fp, fpname):
-        comments = []
-        cursect = None
-        optname = None
-        lineno = 0
-        e = None
-        while True:
-            line = fp.readline()
-            if not line:
-                break
-            lineno += 1
-            if line.strip() == '':
-                if comments:
-                    comments.append('')
-                continue
-
-            if line[0] in '#;':
-                comments.append(line)
-                continue
-
-            if line.split(None, 1)[0].lower() == 'rem' and line[0] in "rR":
-                continue
-            if line[0].isspace() and cursect is not None and optname:
-                value = line.strip()
-                if value:
-                    try:
-                        if isinstance(cursect[optname], (tuple, set)):
-                            _temp_item = list(cursect[optname])
-                            del cursect[optname]
-                            cursect[optname] = _temp_item
-                        elif isinstance(cursect[optname], six.text_type):
-                            _temp_item = [cursect[optname]]
-                            del cursect[optname]
-                            cursect[optname] = _temp_item
-                    except NameError:
-                        if isinstance(cursect[optname], (bytes, str)):
-                            _temp_item = [cursect[optname]]
-                            del cursect[optname]
-                            cursect[optname] = _temp_item
-                    cursect[optname].append(value)
-            else:
-                mo = self.SECTCRE.match(line)
-                if mo:
-                    sectname = mo.group('header')
-                    if sectname in self._sections:
-                        cursect = self._sections[sectname]
-                    elif sectname == 'DEFAULT':
-                        cursect = self._defaults
-                    else:
-                        cursect = self._dict()
-                        self._sections[sectname] = cursect
-                    optname = None
-
-                    comsect = self._comments.setdefault(sectname, {})
-                    if comments:
-                        # NOTE(flaper87): Using none as the key for
-                        # section level comments
-                        comsect[None] = comments
-                        comments = []
-                elif cursect is None:
-                    raise ConfigParser.MissingSectionHeaderError(
-                        fpname,
-                        lineno,
-                        line
-                    )
-                else:
-                    mo = self._optcre.match(line)
-                    if mo:
-                        optname, vi, optval = mo.group('option', 'vi', 'value')
-                        optname = self.optionxform(optname.rstrip())
-                        if optval is not None:
-                            if vi in ('=', ':') and ';' in optval:
-                                pos = optval.find(';')
-                                if pos != -1 and optval[pos - 1].isspace():
-                                    optval = optval[:pos]
-                            optval = optval.strip()
-                            if optval == '""':
-                                optval = ''
-                        cursect[optname] = optval
-                        if comments:
-                            comsect[optname] = comments
-                            comments = []
-                    else:
-                        if not e:
-                            e = ConfigParser.ParsingError(fpname)
-                        e.append(lineno, repr(line))
-        if e:
-            raise e
-        all_sections = [self._defaults]
-        all_sections.extend(self._sections.values())
-        for options in all_sections:
-            for name, val in options.items():
-                if isinstance(val, list):
-                    _temp_item = '\n'.join(val)
-                    del options[name]
-                    options[name] = _temp_item
-
-
-class ActionModule(ActionBase):
-    TRANSFERS_FILES = True
-
-    def return_config_overrides_ini(self,
-                                    config_overrides,
-                                    resultant,
-                                    list_extend=True,
-                                    ignore_none_type=True,
-                                    default_section='DEFAULT'):
-        """Returns string value from a modified config file.
-
-        :param config_overrides: ``dict``
-        :param resultant: ``str`` || ``unicode``
-        :returns: ``str``
-        """
-        # If there is an exception loading the RawConfigParser The config obj
-        #  is loaded again without the extra option. This is being done to
-        #  support older python.
-        try:
-            config = ConfigTemplateParser(
-                allow_no_value=True,
-                dict_type=MultiKeyDict,
-                ignore_none_type=ignore_none_type,
-                default_section=default_section
-            )
-            config.optionxform = str
-        except Exception:
-            config = ConfigTemplateParser(dict_type=MultiKeyDict)
-
-        config_object = StringIO(resultant)
-        config.readfp(config_object)
-        for section, items in config_overrides.items():
-            # If the items value is not a dictionary it is assumed that the
-            #  value is a default item for this config type.
-            if not isinstance(items, dict):
-                if isinstance(items, list):
-                    items = ','.join(to_text(i) for i in items)
-                self._option_write(
-                    config,
-                    'DEFAULT',
-                    section,
-                    items
-                )
-            else:
-                # Attempt to add a section to the config file passing if
-                #  an error is raised that is related to the section
-                #  already existing.
-                try:
-                    config.add_section(section)
-                except (ConfigParser.DuplicateSectionError, ValueError):
-                    pass
-                for key, value in items.items():
-                    try:
-                        self._option_write(config, section, key, value)
-                    except ConfigParser.NoSectionError as exp:
-                        error_msg = str(exp)
-                        error_msg += (
-                            ' Try being more explicit with your override'
-                            'data. Sections are case sensitive.'
-                        )
-                        raise errors.AnsibleModuleError(error_msg)
-        else:
-            config_object.close()
-
-        resultant_stringio = StringIO()
-        try:
-            config.write(resultant_stringio)
-            return resultant_stringio.getvalue()
-        finally:
-            resultant_stringio.close()
-
-    @staticmethod
-    def _option_write(config, section, key, value):
-        config.remove_option(str(section), str(key))
-        try:
-            if not any(list(value.values())):
-                value = tuple(value.keys())
-        except AttributeError:
-            pass
-        if isinstance(value, (tuple, set)):
-            config.set(str(section), str(key), value)
-        elif isinstance(value, set):
-            config.set(str(section), str(key), value)
-        elif isinstance(value, list):
-            config.set(str(section), str(key), ','.join(str(i) for i in value))
-        else:
-            config.set(str(section), str(key), str(value))
-
-    def return_config_overrides_json(self,
-                                     config_overrides,
-                                     resultant,
-                                     list_extend=True,
-                                     ignore_none_type=True,
-                                     default_section='DEFAULT'):
-        """Returns config json
-
-        Its important to note that file ordering will not be preserved as the
-        information within the json file will be sorted by keys.
-
-        :param config_overrides: ``dict``
-        :param resultant: ``str`` || ``unicode``
-        :returns: ``str``
-        """
-        original_resultant = json.loads(resultant)
-        merged_resultant = self._merge_dict(
-            base_items=original_resultant,
-            new_items=config_overrides,
-            list_extend=list_extend,
-            default_section=default_section
-        )
-        return json.dumps(
-            merged_resultant,
-            indent=4,
-            sort_keys=True
-        )
-
-    def return_config_overrides_yaml(self,
-                                     config_overrides,
-                                     resultant,
-                                     list_extend=True,
-                                     ignore_none_type=True,
-                                     default_section='DEFAULT'):
-        """Return config yaml.
-
-        :param config_overrides: ``dict``
-        :param resultant: ``str`` || ``unicode``
-        :returns: ``str``
-        """
-        original_resultant = yaml.safe_load(resultant)
-        merged_resultant = self._merge_dict(
-            base_items=original_resultant,
-            new_items=config_overrides,
-            list_extend=list_extend
-        )
-        return yaml.dump(
-            merged_resultant,
-            Dumper=IDumper,
-            default_flow_style=False,
-            width=1000,
-        )
-
-    def _merge_dict(self, base_items, new_items, list_extend=True):
-        """Recursively merge new_items into base_items.
-
-        :param base_items: ``dict``
-        :param new_items: ``dict``
-        :returns: ``dict``
-        """
-        for key, value in new_items.items():
-            if isinstance(value, dict):
-                base_items[key] = self._merge_dict(
-                    base_items=base_items.get(key, {}),
-                    new_items=value,
-                    list_extend=list_extend
-                )
-            elif (not isinstance(value, int) and
-                  (',' in value or '\n' in value)):
-                base_items[key] = re.split(',|\n', value)
-                base_items[key] = [i.strip() for i in base_items[key] if i]
-            elif isinstance(value, list):
-                if isinstance(base_items.get(key), list) and list_extend:
-                    base_items[key].extend(value)
-                else:
-                    base_items[key] = value
-            elif isinstance(value, (tuple, set)):
-                if isinstance(base_items.get(key), tuple) and list_extend:
-                    base_items[key] += tuple(value)
-                elif isinstance(base_items.get(key), list) and list_extend:
-                    base_items[key].extend(list(value))
-                else:
-                    base_items[key] = value
-            else:
-                base_items[key] = new_items[key]
-        return base_items
-
-    def _load_options_and_status(self, task_vars):
-        """Return options and status from module load."""
-
-        config_type = self._task.args.get('config_type')
-        if config_type not in ['ini', 'yaml', 'json']:
-            return False, dict(
-                failed=True,
-                msg="No valid [ config_type ] was provided. Valid options are"
-                    " ini, yaml, or json."
-            )
-
-        # Access to protected method is unavoidable in Ansible
-        searchpath = [self._loader._basedir]
-
-        if self._task._role:
-            file_path = self._task._role._role_path
-            searchpath.insert(1, C.DEFAULT_ROLES_PATH)
-            searchpath.insert(1, self._task._role._role_path)
-        else:
-            file_path = self._loader.get_basedir()
-
-        user_source = self._task.args.get('src')
-        # (alextricity25) It's possible that the user could pass in a datatype
-        # and not always a string. In this case we don't want the datatype
-        # python representation to be printed out to the file, but rather we
-        # want the serialized version.
-        _user_content = self._task.args.get('content')
-
-        # If the data type of the content input is a dictionary, it's
-        # converted dumped as json if config_type is 'json'.
-        if isinstance(_user_content, dict):
-            if self._task.args.get('config_type') == 'json':
-                _user_content = json.dumps(_user_content)
-
-        user_content = str(_user_content)
-        if not user_source:
-            if not user_content:
-                return False, dict(
-                    failed=True,
-                    msg="No user [ src ] or [ content ] was provided"
-                )
-            else:
-                tmp_content = None
-                fd, tmp_content = tmpfilelib.mkstemp()
-                try:
-                    with open(tmp_content, 'wb') as f:
-                        f.write(user_content.encode())
-                except Exception as err:
-                    os.remove(tmp_content)
-                    raise Exception(err)
-                self._task.args['src'] = source = tmp_content
-        else:
-            source = self._loader.path_dwim_relative(
-                file_path,
-                'templates',
-                user_source
-            )
-        searchpath.insert(1, os.path.dirname(source))
-
-        _dest = self._task.args.get('dest')
-        list_extend = self._task.args.get('list_extend')
-        if not _dest:
-            return False, dict(
-                failed=True,
-                msg="No [ dest ] was provided"
-            )
-        else:
-            # Expand any user home dir specification
-            user_dest = self._remote_expand_user(_dest)
-            if user_dest.endswith(os.sep):
-                user_dest = os.path.join(user_dest, os.path.basename(source))
-
-        # Get ignore_none_type
-        # In some situations(i.e. my.cnf files), INI files can have valueless
-        # options that don't have a '=' or ':' suffix. In these cases,
-        # ConfigParser gives these options a "None" value. If ignore_none_type
-        # is set to true, these key/value options will be ignored, if it's set
-        # to false, then ConfigTemplateParser will write out only the option
-        # name with out the '=' or ':' suffix. The default is true.
-        ignore_none_type = self._task.args.get('ignore_none_type', True)
-
-        default_section = self._task.args.get('default_section', 'DEFAULT')
-
-        return True, dict(
-            source=source,
-            dest=user_dest,
-            config_overrides=self._task.args.get('config_overrides', dict()),
-            config_type=config_type,
-            searchpath=searchpath,
-            list_extend=list_extend,
-            ignore_none_type=ignore_none_type,
-            default_section=default_section
-        )
-
-    def run(self, tmp=None, task_vars=None):
-        """Run the method"""
-
-        try:
-            remote_user = task_vars.get('ansible_user')
-            if not remote_user:
-                remote_user = task_vars.get('ansible_ssh_user')
-            if not remote_user:
-                remote_user = self._play_context.remote_user
-
-            if not tmp:
-                tmp = self._make_tmp_path(remote_user)
-        except TypeError:
-            if not tmp:
-                tmp = self._make_tmp_path()
-
-        _status, _vars = self._load_options_and_status(task_vars=task_vars)
-        if not _status:
-            return _vars
-
-        temp_vars = task_vars.copy()
-        template_host = temp_vars['template_host'] = os.uname()[1]
-        source = temp_vars['template_path'] = _vars['source']
-        temp_vars['template_mtime'] = datetime.datetime.fromtimestamp(
-            os.path.getmtime(source)
-        )
-
-        try:
-            template_uid = temp_vars['template_uid'] = pwd.getpwuid(
-                os.stat(source).st_uid
-            ).pw_name
-        except Exception:
-            template_uid = temp_vars['template_uid'] = os.stat(source).st_uid
-
-        managed_default = C.DEFAULT_MANAGED_STR
-        managed_str = managed_default.format(
-            host=template_host,
-            uid=template_uid,
-            file=to_bytes(source)
-        )
-
-        temp_vars['ansible_managed'] = time.strftime(
-            managed_str,
-            time.localtime(os.path.getmtime(source))
-        )
-        temp_vars['template_fullpath'] = os.path.abspath(source)
-        temp_vars['template_run_date'] = datetime.datetime.now()
-
-        with open(source, 'r') as f:
-            template_data = to_text(f.read())
-
-        self._templar.environment.loader.searchpath = _vars['searchpath']
-        self._templar.set_available_variables(temp_vars)
-        resultant = self._templar.template(
-            template_data,
-            preserve_trailing_newlines=True,
-            escape_backslashes=False,
-            convert_data=False
-        )
-
-        # Access to protected method is unavoidable in Ansible
-        self._templar.set_available_variables(
-            self._templar._available_variables
-        )
-
-        if _vars['config_overrides']:
-            type_merger = getattr(self, CONFIG_TYPES.get(_vars['config_type']))
-            resultant = type_merger(
-                config_overrides=_vars['config_overrides'],
-                resultant=resultant,
-                list_extend=_vars.get('list_extend', True),
-                ignore_none_type=_vars.get('ignore_none_type', True),
-                default_section=_vars.get('default_section', 'DEFAULT')
-            )
-
-        # Re-template the resultant object as it may have new data within it
-        #  as provided by an override variable.
-        resultant = self._templar.template(
-            resultant,
-            preserve_trailing_newlines=True,
-            escape_backslashes=False,
-            convert_data=False
-        )
-
-        # run the copy module
-        new_module_args = self._task.args.copy()
-        # Access to protected method is unavoidable in Ansible
-        transferred_data = self._transfer_data(
-            self._connection._shell.join_path(tmp, 'source'),
-            resultant
-        )
-        if LooseVersion(__ansible_version__) < LooseVersion("2.6"):
-            new_module_args.update(
-                dict(
-                    src=transferred_data,
-                    dest=_vars['dest'],
-                    original_basename=os.path.basename(source),
-                    follow=True,
-                ),
-            )
-        else:
-            new_module_args.update(
-                dict(
-                    src=transferred_data,
-                    dest=_vars['dest'],
-                    _original_basename=os.path.basename(source),
-                    follow=True,
-                ),
-            )
-
-        # Remove data types that are not available to the copy module
-        new_module_args.pop('config_overrides', None)
-        new_module_args.pop('config_type', None)
-        new_module_args.pop('list_extend', None)
-        new_module_args.pop('ignore_none_type', None)
-        new_module_args.pop('default_section', None)
-        # Content from config_template is converted to src
-        new_module_args.pop('content', None)
-
-        # Run the copy module
-        rc = self._execute_module(
-            module_name='copy',
-            module_args=new_module_args,
-            task_vars=task_vars
-        )
-        if self._task.args.get('content'):
-            os.remove(_vars['source'])
-        return rc
index e22f325b092029b75b469f7ed6447d4bc8466b43..21db2c1286585eee0c93d4236763d91c26e47bb6 100644 (file)
   when: not containerized_deployment | bool
 
 - name: "generate {{ cluster }}.conf configuration file"
-  action: config_template
-  args:
+  openstack.config_template.config_template:
     src: "ceph.conf.j2"
     dest: "{{ ceph_conf_key_directory }}/{{ cluster }}.conf"
     owner: "{{ ceph_uid if containerized_deployment | bool else 'ceph' }}"
index 525b8e6a9cd3a9c699c1a592c56f747a39b3fa0c..316134a31c0c0b7122af6f662930686d141fd4bd 100644 (file)
@@ -44,7 +44,7 @@
     - not ansible_facts['os_family'] in ['RedHat', 'Suse']
 
 - name: write grafana.ini
-  config_template:
+  openstack.config_template.config_template:
     src: grafana.ini.j2
     dest: /etc/grafana/grafana.ini
     owner: "{{ grafana_uid }}"
index 8355f69909d2ed56a32e8525e4c6244cb3bb39b4..31a9bc9dbbf011b1253071cb283defe81c2d5588 100644 (file)
@@ -45,7 +45,7 @@
     - ip_version == 'ipv6'
 
 - name: deploy gateway settings, used by the ceph_iscsi_config modules
-  config_template:
+  openstack.config_template.config_template:
     src: "{{ role_path }}/templates/iscsi-gateway.cfg.j2"
     dest: /etc/ceph/iscsi-gateway.cfg
     config_type: ini
index cfe6b2703fe11b2b64608a0180af4f727a61acd9..cc69f9a571dba07276394297ae7ece7fcc874082 100644 (file)
@@ -47,7 +47,7 @@
     - ansible_facts['service_mgr'] == 'systemd'
 
 - name: add ceph-mds systemd service overrides
-  config_template:
+  openstack.config_template.config_template:
     src: "ceph-mds.service.d-overrides.j2"
     dest: "/etc/systemd/system/ceph-mds@.service.d/ceph-mds-systemd-overrides.conf"
     config_overrides: "{{ ceph_mds_systemd_overrides | default({}) }}"
index ba38cf15ce9c0cc3fb5cae5beeb26c7230e24275..7b8eaa314321ffac5dc42017f8b3d26549377a2a 100644 (file)
@@ -8,7 +8,7 @@
     - ansible_facts['service_mgr'] == 'systemd'
 
 - name: add ceph-mgr systemd service overrides
-  config_template:
+  openstack.config_template.config_template:
     src: "ceph-mgr.service.d-overrides.j2"
     dest: "/etc/systemd/system/ceph-mgr@.service.d/ceph-mgr-systemd-overrides.conf"
     config_overrides: "{{ ceph_mgr_systemd_overrides | default({}) }}"
index 8c3b14314f5ba721894de2b17d8baa3361ba14c6..7ecb3600b471701f3f3e17a605c6761b2abea5cf 100644 (file)
@@ -9,7 +9,7 @@
     - ansible_facts['service_mgr'] == 'systemd'
 
 - name: add ceph-mon systemd service overrides
-  config_template:
+  openstack.config_template.config_template:
     src: "ceph-mon.service.d-overrides.j2"
     dest: "/etc/systemd/system/ceph-mon@.service.d/ceph-mon-systemd-overrides.conf"
     config_overrides: "{{ ceph_mon_systemd_overrides | default({}) }}"
index cabbc278ab904045836c2c41cdfbb30d910573b3..f7d68313869605e53e219d7d07cfa24fc96d0704 100644 (file)
@@ -48,8 +48,7 @@
   notify: restart ceph nfss
 
 - name: generate ganesha idmap.conf file
-  action: config_template
-  args:
+  openstack.config_template.config_template:
     src: "idmap.conf.j2"
     dest: "{{ ceph_nfs_idmap_conf }}"
     owner: "root"
index 30417976ced2e146fc3dd2a431800510b04e5817..a0a6468005afd13cac27bf6f9e960dcdb4d3d776 100644 (file)
@@ -30,7 +30,7 @@
     - ansible_facts['service_mgr'] == 'systemd'
 
 - name: add ceph-osd systemd service overrides
-  config_template:
+  openstack.config_template.config_template:
     src: "ceph-osd.service.d-overrides.j2"
     dest: "/etc/systemd/system/ceph-osd@.service.d/ceph-osd-systemd-overrides.conf"
     config_overrides: "{{ ceph_osd_systemd_overrides | default({}) }}"
index 8987009b77880856ad47220921b929fcc9d906a9..ea43aceb66f1e3de8e316cb6b62c7eb151a38400 100644 (file)
@@ -10,7 +10,7 @@
    - "{{ prometheus_data_dir }}"
 
 - name: write prometheus config file
-  config_template:
+  openstack.config_template.config_template:
     src: prometheus.yml.j2
     dest: "{{ prometheus_conf_dir }}/prometheus.yml"
     owner: "{{ prometheus_user_id }}"
@@ -46,7 +46,7 @@
    - "{{ alertmanager_data_dir }}"
 
 - name: write alertmanager config file
-  config_template:
+  openstack.config_template.config_template:
     src: alertmanager.yml.j2
     dest: "{{ alertmanager_conf_dir }}/alertmanager.yml"
     owner: "{{ prometheus_user_id }}"
index cbc32748b6e3abe7c8e6fef99fc40ba58d28345b..c347e70a7956fba7a5b76df1b4f2cc35c271bcb2 100644 (file)
@@ -8,7 +8,7 @@
     - ansible_facts['service_mgr'] == 'systemd'
 
 - name: add ceph-rbd-mirror systemd service overrides
-  config_template:
+  openstack.config_template.config_template:
     src: "ceph-rbd-mirror.service.d-overrides.j2"
     dest: "/etc/systemd/system/ceph-rbd-mirror@.service.d/ceph-rbd-mirror-systemd-overrides.conf"
     config_overrides: "{{ ceph_rbd_mirror_systemd_overrides | default({}) }}"
index 564540a86dd61c41af383ba9d6ee5338244f5633..847524aa909120474b5868e64e5891545833df09 100644 (file)
@@ -6,7 +6,7 @@
   when: ceph_rgw_systemd_overrides is defined
 
 - name: add ceph-rgw systemd service overrides
-  config_template:
+  openstack.config_template.config_template:
     src: "ceph-rgw.service.d-overrides.j2"
     dest: "/etc/systemd/system/ceph-radosgw@.service.d/ceph-radosgw-systemd-overrides.conf"
     config_overrides: "{{ ceph_rgw_systemd_overrides | default({}) }}"
diff --git a/tox.ini b/tox.ini
index 364d87f92a93ef33f99bcea15f97369ca7abbae1..15868173bc78ab76079625d5641a4589d26ff468 100644 (file)
--- a/tox.ini
+++ b/tox.ini
@@ -353,6 +353,7 @@ changedir=
   cephadm_adopt: {toxinidir}/tests/functional/all_daemons{env:CONTAINER_DIR:}
 
 commands=
+  ansible-galaxy install -r {toxinidir}/requirements.yml -v
   rhcs: ansible-playbook -vv -i "localhost," -c local {toxinidir}/tests/functional/rhcs_setup.yml --extra-vars "change_dir={changedir}" --tags "vagrant_setup"
   non_container: ansible-playbook -vv -i "localhost," -c local {toxinidir}/tests/functional/dev_setup.yml --extra-vars "dev_setup={env:DEV_SETUP:False} change_dir={changedir} ceph_dev_branch={env:CEPH_DEV_BRANCH:master} ceph_dev_sha1={env:CEPH_DEV_SHA1:latest}" --tags "vagrant_setup"