From 6c2895fd88a38c5fcde06d86156f72c3eea117c4 Mon Sep 17 00:00:00 2001 From: Brian Coca Date: Tue, 9 Jan 2024 12:07:03 -0500 Subject: [PATCH] ansible-config dedupe ini plugin entries (#82498) added test for ini file integrity, also ensuring no dupes --- changelogs/fragments/dedupe_config_init.yml | 2 ++ lib/ansible/cli/config.py | 36 +++++++++++-------- .../targets/ansible-config/aliases | 2 ++ .../targets/ansible-config/files/ini_dupes.py | 12 +++++++ .../targets/ansible-config/tasks/main.yml | 14 ++++++++ 5 files changed, 52 insertions(+), 14 deletions(-) create mode 100644 changelogs/fragments/dedupe_config_init.yml create mode 100644 test/integration/targets/ansible-config/aliases create mode 100755 test/integration/targets/ansible-config/files/ini_dupes.py create mode 100644 test/integration/targets/ansible-config/tasks/main.yml 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 c2124e210ae..e7457c18f43 100755 --- a/lib/ansible/cli/config.py +++ b/lib/ansible/cli/config.py @@ -313,7 +313,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()): @@ -326,7 +326,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]) @@ -342,37 +342,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}}'"