]> git.apps.os.sepia.ceph.com Git - ceph-ansible.git/commitdiff
Sync config_template with base plugin
authorAndy McCrae <andy.mccrae@gmail.com>
Wed, 25 Jul 2018 09:05:30 +0000 (10:05 +0100)
committermergify[bot] <mergify[bot]@users.noreply.github.com>
Tue, 21 Aug 2018 16:10:33 +0000 (16:10 +0000)
The config_template plugin exists in the ceph-common role so that
config_template will still work with ansible galaxy.

This PR syncs the config_template module from the base of the repo in
plugins/actions to the ceph-common role.

Signed-off-by: Andy McCrae <andy.mccrae@gmail.com>
roles/ceph-common/plugins/actions/_v1_config_template.py [deleted file]
roles/ceph-common/plugins/actions/_v2_config_template.py [deleted file]
roles/ceph-common/plugins/actions/config_template.py

diff --git a/roles/ceph-common/plugins/actions/_v1_config_template.py b/roles/ceph-common/plugins/actions/_v1_config_template.py
deleted file mode 100644 (file)
index 579c7f8..0000000
+++ /dev/null
@@ -1,466 +0,0 @@
-# Copyright 2015, Rackspace US, Inc.
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-try:
-    import ConfigParser
-except ImportError:
-    import configparser as ConfigParser
-import io
-import json
-import os
-import re
-import yaml
-
-from ansible import errors
-from ansible.runner.return_data import ReturnData
-from ansible import utils
-from ansible.utils import template
-
-
-CONFIG_TYPES = {
-    'ini': 'return_config_overrides_ini',
-    'json': 'return_config_overrides_json',
-    'yaml': 'return_config_overrides_yaml'
-}
-
-
-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 set. This set 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': set(['1', '2']), 'c': {'a': 1}, 'b': ['a', 'b', 'c']}
-    """
-    def __setitem__(self, key, value):
-        if key in self:
-            if isinstance(self[key], set):
-                items = self[key]
-                items.add(str(value))
-                super(MultiKeyDict, self).__setitem__(key, items)
-            else:
-                items = [str(value), str(self[key])]
-                super(MultiKeyDict, self).__setitem__(key, set(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 _write(self, fp, section, item, entry):
-        if section:
-            if (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, set):
-            for item in value:
-                item = str(item).replace('\n', '\n\t')
-                entry = "%s = %s\n" % (key, item)
-                self._write(fp, section, 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, value, entry)
-
-    def write(self, fp):
-        if self._defaults:
-            fp.write("[%s]\n" % 'DEFAULT')
-            for key, value in self._defaults.items():
-                self._write_check(fp, key=key, value=value)
-            else:
-                fp.write("\n")
-
-        for section in self._sections:
-            fp.write("[%s]\n" % section)
-            for key, value in self._sections[section].items():
-                self._write_check(fp, key=key, value=value, section=True)
-            else:
-                fp.write("\n")
-
-    def _read(self, fp, fpname):
-        cursect = None
-        optname = None
-        lineno = 0
-        e = None
-        while True:
-            line = fp.readline()
-            if not line:
-                break
-            lineno += 1
-            if line.strip() == '' or line[0] in '#;':
-                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:
-                    if isinstance(cursect[optname], set):
-                        _temp_item = list(cursect[optname])
-                        del cursect[optname]
-                        cursect[optname] = _temp_item
-                    elif isinstance(cursect[optname], (str, unicode)):
-                        _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
-                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
-                    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(object):
-    TRANSFERS_FILES = True
-
-    def __init__(self, runner):
-        self.runner = runner
-
-    @staticmethod
-    def grab_options(complex_args, module_args):
-        """Grab passed options from Ansible complex and module args.
-
-        :param complex_args: ``dict``
-        :param module_args: ``dict``
-        :returns: ``dict``
-        """
-        options = dict()
-        if complex_args:
-            options.update(complex_args)
-
-        options.update(utils.parse_kv(module_args))
-        return options
-
-    @staticmethod
-    def _option_write(config, section, key, value):
-        config.remove_option(str(section), str(key))
-        try:
-            if not any(i for i in value.values()):
-                value = set(value)
-        except AttributeError:
-            pass
-        if 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_ini(self, config_overrides, resultant, list_extend=True):
-        """Returns string value from a modified config file.
-
-        :param config_overrides: ``dict``
-        :param resultant: ``str`` || ``unicode``
-        :returns: ``str``
-        """
-        config = ConfigTemplateParser(
-            dict_type=MultiKeyDict,
-            allow_no_value=True
-        )
-        config.optionxform = str
-        config_object = io.BytesIO(resultant.encode('utf-8'))
-        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(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(str(section))
-                except (ConfigParser.DuplicateSectionError, ValueError):
-                    pass
-                for key, value in items.items():
-                    self._option_write(config, section, key, value)
-        else:
-            config_object.close()
-
-        resultant_bytesio = io.BytesIO()
-        try:
-            config.write(resultant_bytesio)
-            return resultant_bytesio.getvalue()
-        finally:
-            resultant_bytesio.close()
-
-    def return_config_overrides_json(self, config_overrides, resultant, list_extend=True):
-        """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
-        )
-        return json.dumps(
-            merged_resultant,
-            indent=4,
-            sort_keys=True
-        )
-
-    def return_config_overrides_yaml(self, config_overrides, resultant, list_extend=True):
-        """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.safe_dump(
-            merged_resultant,
-            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.iteritems():
-            if isinstance(value, dict):
-                base_items[key] = self._merge_dict(
-                    base_items.get(key, {}),
-                    value
-                )
-            elif ',' 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
-            else:
-                base_items[key] = new_items[key]
-        return base_items
-
-    def run(self, conn, tmp, module_name, module_args, inject,
-            complex_args=None, **kwargs):
-        """Run the method"""
-        if not self.runner.is_playbook:
-            raise errors.AnsibleError(
-                'FAILED: `config_templates` are only available in playbooks'
-            )
-
-        options = self.grab_options(complex_args, module_args)
-        try:
-            source = options['src']
-            dest = options['dest']
-
-            config_overrides = options.get('config_overrides', dict())
-            config_type = options['config_type']
-            assert config_type.lower() in ['ini', 'json', 'yaml']
-        except KeyError as exp:
-            result = dict(failed=True, msg=exp)
-            return ReturnData(conn=conn, comm_ok=False, result=result)
-
-        source_template = template.template(
-            self.runner.basedir,
-            source,
-            inject
-        )
-
-        if '_original_file' in inject:
-            source_file = utils.path_dwim_relative(
-                inject['_original_file'],
-                'templates',
-                source_template,
-                self.runner.basedir
-            )
-        else:
-            source_file = utils.path_dwim(self.runner.basedir, source_template)
-
-        # Open the template file and return the data as a string. This is
-        #  being done here so that the file can be a vault encrypted file.
-        resultant = template.template_from_file(
-            self.runner.basedir,
-            source_file,
-            inject,
-            vault_password=self.runner.vault_pass
-        )
-
-        if config_overrides:
-            type_merger = getattr(self, CONFIG_TYPES.get(config_type))
-            resultant = type_merger(
-                config_overrides=config_overrides,
-                resultant=resultant,
-                list_extend=options.get('list_extend', True)
-            )
-
-        # Retemplate the resultant object as it may have new data within it
-        #  as provided by an override variable.
-        template.template_from_string(
-            basedir=self.runner.basedir,
-            data=resultant,
-            vars=inject,
-            fail_on_undefined=True
-        )
-
-        # Access to protected method is unavoidable in Ansible 1.x.
-        new_module_args = dict(
-            src=self.runner._transfer_str(conn, tmp, 'source', resultant),
-            dest=dest,
-            original_basename=os.path.basename(source),
-            follow=True,
-        )
-
-        module_args_tmp = utils.merge_module_args(
-            module_args,
-            new_module_args
-        )
-
-        # Remove data types that are not available to the copy module
-        complex_args.pop('config_overrides')
-        complex_args.pop('config_type')
-        complex_args.pop('list_extend', None)
-
-        # Return the copy module status. Access to protected method is
-        #  unavoidable in Ansible 1.x.
-        return self.runner._execute_module(
-            conn,
-            tmp,
-            'copy',
-            module_args_tmp,
-            inject=inject,
-            complex_args=complex_args
-        )
diff --git a/roles/ceph-common/plugins/actions/_v2_config_template.py b/roles/ceph-common/plugins/actions/_v2_config_template.py
deleted file mode 100644 (file)
index a9bc2f5..0000000
+++ /dev/null
@@ -1,572 +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)
-__metaclass__ = type
-
-try:
-    import ConfigParser
-except ImportError:
-    import configparser as ConfigParser
-
-import datetime
-import io
-import json
-import os
-import pwd
-import re
-import time
-import yaml
-
-
-from ansible.plugins.action import ActionBase
-from ansible.utils.unicode import to_bytes, to_unicode
-from ansible import constants as C
-from ansible import errors
-
-
-CONFIG_TYPES = {
-    'ini': 'return_config_overrides_ini',
-    'json': 'return_config_overrides_json',
-    'yaml': 'return_config_overrides_yaml'
-}
-
-
-def _convert_2_string(item):
-    """Return byte strings for all items.
-
-    This will convert everything within a dict, list or unicode string such
-    that the values will be encode('utf-8') where applicable.
-    """
-
-    if isinstance(item, dict):
-        # Old style dict comprehension for legacy python support
-        return dict(
-            (_convert_2_string(key), _convert_2_string(value))
-            for key, value in item.iteritems()
-        )
-    elif isinstance(item, list):
-        return [_convert_2_string(i) for i in item]
-    elif isinstance(item, tuple):
-        return tuple([_convert_2_string(i) for i in item])
-    elif isinstance(item, set):
-        return item
-    else:
-        try:
-            return item.encode('utf-8')
-        except AttributeError:
-            return str(item)
-
-
-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 set. This set 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': set(['1', '2']), 'c': {'a': 1}, 'b': ['a', 'b', 'c']}
-    """
-    def __setitem__(self, key, value):
-        if key in self:
-            if isinstance(self[key], set):
-                items = self[key]
-                items.add(str(value))
-                super(MultiKeyDict, self).__setitem__(key, items)
-            else:
-                items = [str(value), str(self[key])]
-                super(MultiKeyDict, self).__setitem__(key, set(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 _write(self, fp, section, item, entry):
-        if section:
-            if (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, set):
-            for item in value:
-                item = str(item).replace('\n', '\n\t')
-                entry = "%s = %s\n" % (key, item)
-                self._write(fp, section, 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, value, entry)
-
-    def write(self, fp):
-        if self._defaults:
-            fp.write("[%s]\n" % 'DEFAULT')
-            for key, value in self._defaults.items():
-                self._write_check(fp, key=key, value=value)
-            else:
-                fp.write("\n")
-
-        for section in self._sections:
-            fp.write("[%s]\n" % section)
-            for key, value in self._sections[section].items():
-                self._write_check(fp, key=key, value=value, section=True)
-            else:
-                fp.write("\n")
-
-    def _read(self, fp, fpname):
-        cursect = None
-        optname = None
-        lineno = 0
-        e = None
-        while True:
-            line = fp.readline()
-            if not line:
-                break
-            lineno += 1
-            if line.strip() == '' or line[0] in '#;':
-                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:
-                    if isinstance(cursect[optname], set):
-                        _temp_item = list(cursect[optname])
-                        del cursect[optname]
-                        cursect[optname] = _temp_item
-                    elif isinstance(cursect[optname], (str, unicode)):
-                        _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
-                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
-                    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):
-        """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
-            )
-            config.optionxform = str
-        except Exception:
-            config = ConfigTemplateParser(dict_type=MultiKeyDict)
-
-        config_object = io.BytesIO(str(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(_convert_2_string(items))
-                self._option_write(
-                    config,
-                    'DEFAULT',
-                    str(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.encode('utf-8'))
-                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_bytesio = io.BytesIO()
-        try:
-            config.write(resultant_bytesio)
-            return resultant_bytesio.getvalue()
-        finally:
-            resultant_bytesio.close()
-
-    @staticmethod
-    def _option_write(config, section, key, value):
-        config.remove_option(str(section), str(key))
-        try:
-            if not any(i for i in value.values()):
-                value = set(value)
-        except AttributeError:
-            pass
-        if 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):
-        """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
-        )
-        return json.dumps(
-            merged_resultant,
-            indent=4,
-            sort_keys=True
-        )
-
-    def return_config_overrides_yaml(self, config_overrides, resultant, list_extend=True):
-        """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.safe_dump(
-            merged_resultant,
-            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.iteritems():
-            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
-            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')
-        if not user_source:
-            return False, dict(
-                failed=True,
-                msg="No user provided [ src ] was provided"
-            )
-        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))
-
-        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
-        )
-
-    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_unicode(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)
-            )
-
-        # 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
-        )
-        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)
-
-        # Run the copy module
-        return self._execute_module(
-            module_name='copy',
-            module_args=new_module_args,
-            task_vars=task_vars
-        )
index cc30ded6c8c789c40863995f73627928e6a4f1c3..e88488fb3efc0b28724458f5a28786969bb62728 100644 (file)
-# Copyright 2015, Rackspace US, Inc.
+# (c) 2015, Kevin Carter <kevin.carter@rackspace.com>
 #
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
+# This file is part of Ansible
 #
-#     http://www.apache.org/licenses/LICENSE-2.0
+# 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.
 #
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
+# 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 sys
+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__
 
-# This appends the sys path with the file path which is used for the
-#  import of the specific verion of the config_template action plugin
-#  needed based on the ansible version calling the plugin.
-sys.path.append(os.path.dirname(__file__))
+__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)
 
-if LooseVersion(__ansible_version__) < LooseVersion("2.0"):
-    from _v1_config_template import *
-else:
-    from _v2_config_template import *
+        # 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