From 099422700db2f618aa6c43353cbad0f83458dedf Mon Sep 17 00:00:00 2001 From: Alexei Znamensky Date: Sat, 8 Nov 2025 16:13:43 +1300 Subject: [PATCH 1/3] locale_gen: search for available locales in /usr/local as well --- plugins/modules/locale_gen.py | 43 +++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/plugins/modules/locale_gen.py b/plugins/modules/locale_gen.py index b555fe0d66a..cc1ba22f78b 100644 --- a/plugins/modules/locale_gen.py +++ b/plugins/modules/locale_gen.py @@ -85,7 +85,7 @@ ETC_LOCALE_GEN = "/etc/locale.gen" VAR_LIB_LOCALES = "/var/lib/locales/supported.d" VAR_LIB_LOCALES_LOCAL = os.path.join(VAR_LIB_LOCALES, "local") -SUPPORTED_LOCALES = "/usr/share/i18n/SUPPORTED" +SUPPORTED_LOCALES = ["/usr/share/i18n/SUPPORTED", "/usr/local/share/i18n/SUPPORTED"] LOCALE_NORMALIZATION = { ".utf8": ".UTF-8", ".eucjp": ".EUC-JP", @@ -111,7 +111,7 @@ class LocaleGen(StateModuleHelper): ) def __init_module__(self): - self.MECHANISMS = dict( + self.mechanisms = dict( ubuntu_legacy=dict( available=SUPPORTED_LOCALES, apply_change=self.apply_change_ubuntu_legacy, @@ -156,19 +156,21 @@ def assert_available(self): checking either : * if the locale is present in /etc/locales.gen * or if the locale is present in /usr/share/i18n/SUPPORTED""" - regexp = r"^\s*#?\s*(?P\S+[\._\S]+) (?P\S+)\s*$" - locales_available = self.MECHANISMS[self.vars.mechanism]["available"] - re_compiled = re.compile(regexp) - with open(locales_available, "r") as fd: - lines = fd.readlines() - res = [re_compiled.match(line) for line in lines] - self.vars.set("available_lines", lines, verbosity=4) + self.vars.set("available_lines", [], verbosity=4) + available_locale_entry_re_matches = [] + for locale_path in self.mechanisms[self.vars.mechanism]["available"]: + if os.path.exists(locale_path): + with open(locale_path, "r") as fd: + self.vars.available_lines.extend(fd.readlines()) + + re_compiled = re.compile(r"^\s*#?\s*(?P\S+[\._\S]+) (?P\S+)\s*$") + available_locale_entry_re_matches.extend([re_compiled.match(line) for line in self.vars.available_lines]) locales_not_found = [] for locale in self.vars.name: # Check if the locale is not found in any of the matches - if not any(match and match.group("locale") == locale for match in res): + if not any(match and match.group("locale") == locale for match in available_locale_entry_re_matches): locales_not_found.append(locale) # locale may be installed but not listed in the file, for example C.UTF-8 in some systems @@ -219,38 +221,41 @@ def set_locale_glibc(self, names, enabled=True): re_search = re.compile(search_string) locale_regexes.append([re_search, new_string]) - for i in range(len(lines)): + def search_replace(line): for [search, replace] in locale_regexes: - lines[i] = search.sub(replace, lines[i]) + line = search.sub(replace, line) + return line + + lines = [search_replace(line) for line in lines] # Write the modified content back to the file with open(ETC_LOCALE_GEN, "w") as fw: fw.writelines(lines) - def apply_change_glibc(self, targetState, names): + def apply_change_glibc(self, target_state, names): """Create or remove locale. Keyword arguments: - targetState -- Desired state, either present or absent. + target_state -- Desired state, either present or absent. names -- Names list including encoding such as de_CH.UTF-8. """ - self.set_locale_glibc(names, enabled=(targetState == "present")) + self.set_locale_glibc(names, enabled=(target_state == "present")) runner = locale_gen_runner(self.module) with runner() as ctx: ctx.run() - def apply_change_ubuntu_legacy(self, targetState, names): + def apply_change_ubuntu_legacy(self, target_state, names): """Create or remove locale. Keyword arguments: - targetState -- Desired state, either present or absent. + target_state -- Desired state, either present or absent. names -- Name list including encoding such as de_CH.UTF-8. """ runner = locale_gen_runner(self.module) - if targetState == "present": + if target_state == "present": # Create locale. # Ubuntu's patched locale-gen automatically adds the new locale to /var/lib/locales/supported.d/local with runner() as ctx: @@ -273,7 +278,7 @@ def apply_change_ubuntu_legacy(self, targetState, names): def __state_fallback__(self): if self.vars.state_tracking == self.vars.state: return - self.MECHANISMS[self.vars.mechanism]["apply_change"](self.vars.state, self.vars.name) + self.mechanisms[self.vars.mechanism]["apply_change"](self.vars.state, self.vars.name) def main(): From 2a1c9c7b1022f7ef69bce78905bd8c229dfd4322 Mon Sep 17 00:00:00 2001 From: Alexei Znamensky Date: Sat, 8 Nov 2025 17:06:03 +1300 Subject: [PATCH 2/3] better var name --- plugins/modules/locale_gen.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/modules/locale_gen.py b/plugins/modules/locale_gen.py index cc1ba22f78b..9d593c49f6c 100644 --- a/plugins/modules/locale_gen.py +++ b/plugins/modules/locale_gen.py @@ -164,8 +164,8 @@ def assert_available(self): with open(locale_path, "r") as fd: self.vars.available_lines.extend(fd.readlines()) - re_compiled = re.compile(r"^\s*#?\s*(?P\S+[\._\S]+) (?P\S+)\s*$") - available_locale_entry_re_matches.extend([re_compiled.match(line) for line in self.vars.available_lines]) + re_locale_entry = re.compile(r"^\s*#?\s*(?P\S+[\._\S]+) (?P\S+)\s*$") + available_locale_entry_re_matches.extend([re_locale_entry.match(line) for line in self.vars.available_lines]) locales_not_found = [] for locale in self.vars.name: From 1fad765d36e78a24059669332e980aad97dfdd6c Mon Sep 17 00:00:00 2001 From: Alexei Znamensky Date: Sat, 8 Nov 2025 23:42:40 +1300 Subject: [PATCH 3/3] add test for /usr/local --- .../targets/locale_gen/files/en_US@iso | 120 ++++++++++++++++++ .../locale_gen/tasks/11046-usrlocal.yml | 79 ++++++++++++ .../targets/locale_gen/tasks/main.yml | 3 + 3 files changed, 202 insertions(+) create mode 100644 tests/integration/targets/locale_gen/files/en_US@iso create mode 100644 tests/integration/targets/locale_gen/tasks/11046-usrlocal.yml diff --git a/tests/integration/targets/locale_gen/files/en_US@iso b/tests/integration/targets/locale_gen/files/en_US@iso new file mode 100644 index 00000000000..23b234048d5 --- /dev/null +++ b/tests/integration/targets/locale_gen/files/en_US@iso @@ -0,0 +1,120 @@ +comment_char % +escape_char / + +% This file is part of the GNU C Library and contains locale data. +% The Free Software Foundation does not claim any copyright interest +% in the locale data contained in this file. The foregoing does not +% affect the license of the GNU C Library as a whole. It does not +% exempt you from the conditions of the license if your use would +% otherwise be governed by that license. + +LC_IDENTIFICATION +title "English locale for the USA with ISO formats" +language "American English" + +category "i18n:2012";LC_IDENTIFICATION +category "i18n:2012";LC_CTYPE +category "i18n:2012";LC_COLLATE +category "i18n:2012";LC_TIME +category "i18n:2012";LC_NUMERIC +category "i18n:2012";LC_MONETARY +category "i18n:2012";LC_MESSAGES +category "i18n:2012";LC_PAPER +category "i18n:2012";LC_NAME +category "i18n:2012";LC_ADDRESS +category "i18n:2012";LC_TELEPHONE +category "i18n:2012";LC_MEASUREMENT +END LC_IDENTIFICATION + +LC_TIME +day "Sunday";/ + "Monday";/ + "Tuesday";/ + "Wednesday";/ + "Thursday";/ + "Friday";/ + "Saturday" +abday "Sun";/ + "Mon";/ + "Tue";/ + "Wed";/ + "Thu";/ + "Fri";/ + "Sat" + +mon "January";/ + "February";/ + "March";/ + "April";/ + "May";/ + "June";/ + "July";/ + "August";/ + "September";/ + "October";/ + "November";/ + "December" +abmon "Jan";/ + "Feb";/ + "Mar";/ + "Apr";/ + "May";/ + "Jun";/ + "Jul";/ + "Aug";/ + "Sep";/ + "Oct";/ + "Nov";/ + "Dec" + +date_fmt "%a %d %b %Y %T %Z" +d_t_fmt "%a %d %b %Y %T" +d_fmt "%Y-%m-%d" +t_fmt "%T" +t_fmt_ampm "" +am_pm "";"" +week 7;19971130;4 +first_weekday 2 +END LC_TIME + +LC_CTYPE +copy "en_US" +END LC_CTYPE + +LC_COLLATE +copy "en_US" +END LC_COLLATE + +LC_MONETARY +copy "en_US" +END LC_MONETARY + +LC_NUMERIC +decimal_point "." +thousands_sep "," +grouping 3;3 +END LC_NUMERIC + +LC_MESSAGES +copy "en_US" +END LC_MESSAGES + +LC_PAPER +copy "i18n" +END LC_PAPER + +LC_TELEPHONE +copy "en_US" +END LC_TELEPHONE + +LC_MEASUREMENT +copy "i18n" +END LC_MEASUREMENT + +LC_NAME +copy "en_US" +END LC_NAME + +LC_ADDRESS +copy "en_US" +END LC_ADDRESS diff --git a/tests/integration/targets/locale_gen/tasks/11046-usrlocal.yml b/tests/integration/targets/locale_gen/tasks/11046-usrlocal.yml new file mode 100644 index 00000000000..917832a6fb5 --- /dev/null +++ b/tests/integration/targets/locale_gen/tasks/11046-usrlocal.yml @@ -0,0 +1,79 @@ +--- +# Copyright (c) Ansible Project +# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt) +# SPDX-License-Identifier: GPL-3.0-or-later + +- name: Is the locale we're going to test against installed? + command: locale -a + register: initial_state + +- name: Make sure the locale is not installed (BEGIN) + community.general.locale_gen: + name: en_US@iso + state: absent + ignore_errors: true + register: not_available + +- name: Ensure /usr/local/share/i18n/ + ansible.builtin.file: + path: /usr/local/share/i18n/locales + state: directory + mode: '0755' + +- name: Back up /etc/locale.gen + ansible.builtin.copy: + src: /etc/locale.gen + dest: /etc/locale.gen.bkp + +- name: Add line to /etc/locale.gen + ansible.builtin.shell: > + echo "# en_US@iso UTF-8" >> /etc/locale.gen + +- name: Copy custom locale + ansible.builtin.copy: + dest: /usr/local/share/i18n/locales/en_US@iso + src: en_US@iso + mode: '0644' + +- name: Add custom locale to SUPPORTED + ansible.builtin.copy: + dest: /usr/local/share/i18n/SUPPORTED + content: | + en_US@iso UTF-8 + mode: '0644' + +- name: Make sure the locale is available + community.general.locale_gen: + name: en_US@iso + state: absent + register: available + +- name: Make sure the locale is installed + community.general.locale_gen: + name: en_US@iso + state: present + register: installed + +- name: Check assertions + ansible.builtin.assert: + that: + - not_available is failed + - > + "locales you have entered are not available on your system: en_US@iso" in not_available.msg + - available is not changed + - installed is changed + +- name: Make sure the locale is not installed (END) + community.general.locale_gen: + name: en_US@iso + state: absent + +- name: Remove /usr/local/share/i18n/ + ansible.builtin.file: + path: /usr/local/share/i18n/ + state: absent + +- name: Restore /etc/locale.gen + ansible.builtin.copy: + src: /etc/locale.gen.bkp + dest: /etc/locale.gen diff --git a/tests/integration/targets/locale_gen/tasks/main.yml b/tests/integration/targets/locale_gen/tasks/main.yml index d24ef5d29dd..76528af2ec6 100644 --- a/tests/integration/targets/locale_gen/tasks/main.yml +++ b/tests/integration/targets/locale_gen/tasks/main.yml @@ -17,3 +17,6 @@ loop: "{{ locale_list_basic }}" loop_control: loop_var: locale_basic + +- name: Run tests for 11046 + ansible.builtin.include_tasks: 11046-usrlocal.yml