diff --git a/changelogs/fragments/dedupe_config_init.yml b/changelogs/fragments/dedupe_config_init.yml new file mode 100644 index 00000000000..16306fc13fa --- /dev/null +++ b/changelogs/fragments/dedupe_config_init.yml @@ -0,0 +1,2 @@ +bugfixes: + - ansible-config init will now dedupe ini entries from plugins. diff --git a/lib/ansible/cli/config.py b/lib/ansible/cli/config.py index 9c9d47b36ff..f394ef7c1ec 100755 --- a/lib/ansible/cli/config.py +++ b/lib/ansible/cli/config.py @@ -314,7 +314,7 @@ class ConfigCLI(CLI): return data - def _get_settings_ini(self, settings): + def _get_settings_ini(self, settings, seen): sections = {} for o in sorted(settings.keys()): @@ -327,7 +327,7 @@ class ConfigCLI(CLI): if not opt.get('description'): # its a plugin - new_sections = self._get_settings_ini(opt) + new_sections = self._get_settings_ini(opt, seen) for s in new_sections: if s in sections: sections[s].extend(new_sections[s]) @@ -343,37 +343,45 @@ class ConfigCLI(CLI): if 'ini' in opt and opt['ini']: entry = opt['ini'][-1] + if entry['section'] not in seen: + seen[entry['section']] = [] if entry['section'] not in sections: sections[entry['section']] = [] - default = opt.get('default', '') - if opt.get('type', '') == 'list' and not isinstance(default, string_types): - # python lists are not valid ini ones - default = ', '.join(default) - elif default is None: - default = '' + # avoid dupes + if entry['key'] not in seen[entry['section']]: + seen[entry['section']].append(entry['key']) + + default = opt.get('default', '') + if opt.get('type', '') == 'list' and not isinstance(default, string_types): + # python lists are not valid ini ones + default = ', '.join(default) + elif default is None: + default = '' + + if context.CLIARGS['commented']: + entry['key'] = ';%s' % entry['key'] - if context.CLIARGS['commented']: - entry['key'] = ';%s' % entry['key'] + key = desc + '\n%s=%s' % (entry['key'], default) - key = desc + '\n%s=%s' % (entry['key'], default) - sections[entry['section']].append(key) + sections[entry['section']].append(key) return sections def execute_init(self): """Create initial configuration""" + seen = {} data = [] config_entries = self._list_entries_from_args() plugin_types = config_entries.pop('PLUGINS', None) if context.CLIARGS['format'] == 'ini': - sections = self._get_settings_ini(config_entries) + sections = self._get_settings_ini(config_entries, seen) if plugin_types: for ptype in plugin_types: - plugin_sections = self._get_settings_ini(plugin_types[ptype]) + plugin_sections = self._get_settings_ini(plugin_types[ptype], seen) for s in plugin_sections: if s in sections: sections[s].extend(plugin_sections[s]) diff --git a/test/integration/targets/ansible-config/aliases b/test/integration/targets/ansible-config/aliases new file mode 100644 index 00000000000..1d28bdb2aa3 --- /dev/null +++ b/test/integration/targets/ansible-config/aliases @@ -0,0 +1,2 @@ +shippable/posix/group5 +context/controller diff --git a/test/integration/targets/ansible-config/files/ini_dupes.py b/test/integration/targets/ansible-config/files/ini_dupes.py new file mode 100755 index 00000000000..ed42e6acb80 --- /dev/null +++ b/test/integration/targets/ansible-config/files/ini_dupes.py @@ -0,0 +1,12 @@ +#!/usr/bin/env python +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import annotations + +import configparser +import sys + + +ini_file = sys.argv[1] +c = configparser.ConfigParser(strict=True, inline_comment_prefixes=(';',)) +c.read_file(open(ini_file)) diff --git a/test/integration/targets/ansible-config/tasks/main.yml b/test/integration/targets/ansible-config/tasks/main.yml new file mode 100644 index 00000000000..a894dd45cf5 --- /dev/null +++ b/test/integration/targets/ansible-config/tasks/main.yml @@ -0,0 +1,14 @@ +- name: test ansible-config for valid output and no dupes + block: + - name: Create temporary file + tempfile: + path: '{{output_dir}}' + state: file + suffix: temp.ini + register: ini_tempfile + + - name: run config full dump + shell: ansible-config init -t all > {{ini_tempfile.path}} + + - name: run ini tester, for correctness and dupes + shell: "{{ansible_playbook_python}} '{{role_path}}/files/ini_dupes.py' '{{ini_tempfile.path}}'"