Repository: ansible-collections/community.general

Issue:
"locale_gen" module fails on CentOS 6, CentOS 7 and RHEL 8 (Rocky)
### Summary

When using module "locale_gen" for machines with CentOS 6 or CentOS 7 or Rocky Linux 8 get fail message "/etc/locale.gen and /var/lib/locales/supported.d/local are missing. Is the package locales installed ?"
But there is no such package for CentOS. I think this module can be worked for supporting RHEL, or there must be listing in module description with supported and not-supported OS.

Mostly migrating this original report that got closed without fix: https://github.com/ansible/ansible/issues/44708

### Issue Type

Bug Report

### Component Name

locale_gen

### Ansible Version

```console (paste below)
ansible [core 2.11.6] 
  config file = None
  configured module search path = ['/home/silviu/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /home/silviu/.local/lib/python3.10/site-packages/ansible
  ansible collection location = /home/silviu/.ansible/collections:/usr/share/ansible/collections
  executable location = /home/silviu/.local/bin/ansible
  python version = 3.10.2 (main, Jan 17 2022, 00:00:00) [GCC 11.2.1 20211203 (Red Hat 11.2.1-7)]
  jinja version = 3.0.2
  libyaml = True

```


### Community.general Version

```console (paste below)
$ ansible-galaxy collection list community.general

```


### Configuration

```console (paste below)
$ ansible-config dump --only-changed

```


### OS / Environment

Rocky Linux 8 (RHEL 8)

### Steps to Reproduce

<!--- Paste example playbooks or commands between quotes below -->
```yaml (paste below)
locales:
  - en_GB.UTF-8
  - en_US.UTF-8

- name: Generate localisation files
  hosts: host.domain
  tasks:
    locale_gen:
      name: "{{ item }}"
      state: present
    loop: "{{ locales }}"
```


### Expected Results

Successful task execution.
Module "locale_gen" must check ansible_os_family and ansible_distribution release and run accordingle right locale generations commands (localedef), supporting not only Debian-based OS, but RHEL-based also.

### Actual Results

```console (paste below)
<host.domain> ESTABLISH SSH CONNECTION FOR USER: None
<host.domain> SSH: EXEC ssh -vvv -C -o ControlMaster=auto -o ControlPersist=60s -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o ConnectTimeout=10 -o ControlPath=/home/user/.ansible/cp/5a62727ccc host.domain '/bin/sh -c '"'"'echo ~ && sleep 0'"'"''
<host.domain> (0, '/home/user\n', 'OpenSSH_7.6p1 Ubuntu-4, OpenSSL 1.0.2n  7 Dec 2017\r\ndebug1: Reading configuration data /home/user/.ssh/config\r\ndebug1: Reading configuration data /etc/ssh/ssh_config\r\ndebug1: /etc/ssh/ssh_config line 19: Applying options for *\r\ndebug1: auto-mux: Trying existing master\r\ndebug2: fd 3 setting O_NONBLOCK\r\ndebug2: mux_client_hello_exchange: master version 4\r\ndebug3: mux_client_forwards: request forwardings: 0 local, 0 remote\r\ndebug3: mux_client_request_session: entering\r\ndebug3: mux_client_request_alive: entering\r\ndebug3: mux_client_request_alive: done pid = 2991\r\ndebug3: mux_client_request_session: session request sent\r\ndebug1: mux_client_request_session: master session id: 2\r\ndebug3: mux_client_read_packet: read header failed: Broken pipe\r\ndebug2: Received exit status from master 0\r\n')
<host.domain> ESTABLISH SSH CONNECTION FOR USER: None
<host.domain> SSH: EXEC ssh -vvv -C -o ControlMaster=auto -o ControlPersist=60s -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o ConnectTimeout=10 -o ControlPath=/home/user/.ansible/cp/5a62727ccc host.domain '/bin/sh -c '"'"'( umask 77 && mkdir -p "` echo /home/user/.ansible/tmp/ansible-tmp-1535354913.52-175980622514910 `" && echo ansible-tmp-1535354913.52-175980622514910="` echo /home/user/.ansible/tmp/ansible-tmp-1535354913.52-175980622514910 `" ) && sleep 0'"'"''
<host.domain> (0, 'ansible-tmp-1535354913.52-175980622514910=/home/user/.ansible/tmp/ansible-tmp-1535354913.52-175980622514910\n', 'OpenSSH_7.6p1 Ubuntu-4, OpenSSL 1.0.2n  7 Dec 2017\r\ndebug1: Reading configuration data /home/user/.ssh/config\r\ndebug1: Reading configuration data /etc/ssh/ssh_config\r\ndebug1: /etc/ssh/ssh_config line 19: Applying options for *\r\ndebug1: auto-mux: Trying existing master\r\ndebug2: fd 3 setting O_NONBLOCK\r\ndebug2: mux_client_hello_exchange: master version 4\r\ndebug3: mux_client_forwards: request forwardings: 0 local, 0 remote\r\ndebug3: mux_client_request_session: entering\r\ndebug3: mux_client_request_alive: entering\r\ndebug3: mux_client_request_alive: done pid = 2991\r\ndebug3: mux_client_request_session: session request sent\r\ndebug1: mux_client_request_session: master session id: 2\r\ndebug3: mux_client_read_packet: read header failed: Brokenpipe\r\ndebug2: Received exit status from master 0\r\n')
Using module file /usr/lib/python2.7/dist-packages/ansible/modules/system/locale_gen.py
<host.domain> PUT /home/user/.ansible/tmp/ansible-local-2958jky98t/tmpAmb995 TO /home/user/.ansible/tmp/ansible-tmp-1535354913.52-175980622514910/locale_gen.py
<host.domain> SSH: EXEC sftp -b - -vvv -C -o ControlMaster=auto -o ControlPersist=60s -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o ConnectTimeout=10 -o ControlPath=/home/user/.ansible/cp/5a62727ccc '[host.domain]'
<host.domain> (0, 'sftp> put /home/user/.ansible/tmp/ansible-local-2958jky98t/tmpAmb995 /home/user/.ansible/tmp/ansible-tmp-1535354913.52-175980622514910/locale_gen.py\n', 'OpenSSH_7.6p1 Ubuntu-4, OpenSSL 1.0.2n  7 Dec 2017\r\ndebug1: Reading configuration data /home/user/.ssh/config\r\ndebug1: Reading configuration data /etc/ssh/ssh_config\r\ndebug1: /etc/ssh/ssh_config line 19: Applying options for *\r\ndebug1: auto-mux: Trying existing master\r\ndebug2: fd 3 setting O_NONBLOCK\r\ndebug2: mux_client_hello_exchange: master version 4\r\ndebug3: mux_client_forwards: request forwardings: 0 local, 0 remote\r\ndebug3: mux_client_request_session: entering\r\ndebug3: mux_client_request_alive: entering\r\ndebug3: mux_client_request_alive: done pid = 2991\r\ndebug3: mux_client_request_session: session request sent\r\ndebug1: mux_client_request_session: master session id: 2\r\ndebug2: Remote version: 3\r\ndebug2: Server supports extension "posix-rename@openssh.com" revision 1\r\ndebug2: Server supports extension "statvfs@openssh.com" revision 2\r\ndebug2: Server supports extension "fstatvfs@openssh.com" revision 2\r\ndebug3: Sent message fd 5 T:16 I:1\r\ndebug3: SSH_FXP_REALPATH . -> /home/user size 0\r\ndebug3: Looking up /home/user/.ansible/tmp/ansible-local-2958jky98t/tmpAmb995\r\ndebug3: Sent message fd 5 T:17 I:2\r\ndebug3: Received stat reply T:101 I:2\r\ndebug1: Couldn\'t stat remote file: No such file or directory\r\ndebug3: Sent message SSH2_FXP_OPEN I:3 P:/home/user/.ansible/tmp/ansible-tmp-1535354913.52-175980622514910/locale_gen.py\r\ndebug3: Sent message SSH2_FXP_WRITE I:4 O:0 S:32768\r\ndebug3: SSH2_FXP_STATUS 0\r\ndebug3: In write loop, ack for 4 32768 bytes at 0\r\ndebug3: Sent message SSH2_FXP_WRITE I:5 O:32768 S:32768\r\ndebug3: Sent message SSH2_FXP_WRITE I:6 O:65536 S:2459\r\ndebug3: SSH2_FXP_STATUS 0\r\ndebug3: In write loop, ack for 5 32768 bytes at 32768\r\ndebug3: SSH2_FXP_STATUS 0\r\ndebug3: In write loop, ack for 6 2459 bytes at 65536\r\ndebug3: Sent message SSH2_FXP_CLOSE I:4\r\ndebug3: SSH2_FXP_STATUS 0\r\ndebug3: mux_client_read_packet: read header failed: Broken pipe\r\ndebug2: Received exit statusfrom master 0\r\n')
<host.domain> ESTABLISH SSH CONNECTION FOR USER: None
<host.domain> SSH: EXEC ssh -vvv -C -o ControlMaster=auto -o ControlPersist=60s -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o ConnectTimeout=10 -o ControlPath=/home/user/.ansible/cp/5a62727ccc host.domain '/bin/sh -c '"'"'chmod u+x /home/user/.ansible/tmp/ansible-tmp-1535354913.52-175980622514910/ /home/user/.ansible/tmp/ansible-tmp-1535354913.52-175980622514910/locale_gen.py && sleep 0'"'"''
<host.domain> (0, '', 'OpenSSH_7.6p1 Ubuntu-4, OpenSSL 1.0.2n  7 Dec 2017\r\ndebug1: Reading configuration data /home/user/.ssh/config\r\ndebug1: Reading configuration data /etc/ssh/ssh_config\r\ndebug1: /etc/ssh/ssh_config line 19: Applying options for *\r\ndebug1: auto-mux: Trying existing master\r\ndebug2: fd 3 setting O_NONBLOCK\r\ndebug2: mux_client_hello_exchange: master version 4\r\ndebug3: mux_client_forwards: request forwardings: 0 local, 0 remote\r\ndebug3: mux_client_request_session: entering\r\ndebug3: mux_client_request_alive: entering\r\ndebug3: mux_client_request_alive: done pid = 2991\r\ndebug3: mux_client_request_session: session request sent\r\ndebug1: mux_client_request_session: master session id: 2\r\ndebug3: mux_client_read_packet: read header failed: Broken pipe\r\ndebug2: Received exit status from master 0\r\n')
<host.domain> ESTABLISH SSH CONNECTION FOR USER: None
<host.domain> SSH: EXEC ssh -vvv -C -o ControlMaster=auto -o ControlPersist=60s -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o ConnectTimeout=10 -o ControlPath=/home/user/.ansible/cp/5a62727ccc -tt host.domain '/bin/sh -c '"'"'sudo -H -S  -p "[sudo via ansible, key=piwtilauojopifgeaxgceplobgdzgioq] password: " -u root /bin/sh -c '"'"'"'"'"'"'"'"'echo BECOME-SUCCESS-piwtilauojopifgeaxgceplobgdzgioq; /usr/bin/python /home/user/.ansible/tmp/ansible-tmp-1535354913.52-175980622514910/locale_gen.py'"'"'"'"'"'"'"'"' && sleep 0'"'"''
Escalation succeeded
<host.domain> (1, '\r\n\r\n{"msg": "/etc/locale.gen and /var/lib/locales/supported.d/local are missing. Is the package \\"locales\\" installed?", "failed": true, "invocation": {"module_args": {"state": "present", "name": "en_US.UTF-8"}}}\r\n', 'OpenSSH_7.6p1 Ubuntu-4, OpenSSL 1.0.2n  7 Dec 2017\r\ndebug1: Reading configuration data /home/user/.ssh/config\r\ndebug1: Reading configuration data /etc/ssh/ssh_config\r\ndebug1: /etc/ssh/ssh_config line 19: Applying options for *\r\ndebug1: auto-mux: Trying existing master\r\ndebug2: fd 3 setting O_NONBLOCK\r\ndebug2: mux_client_hello_exchange: master version 4\r\ndebug3: mux_client_forwards: request forwardings: 0 local, 0 remote\r\ndebug3: mux_client_request_session: entering\r\ndebug3: mux_client_request_alive: entering\r\ndebug3: mux_client_request_alive: done pid = 2991\r\ndebug3: mux_client_request_session: session request sent\r\ndebug1: mux_client_request_session: master session id: 2\r\ndebug3: mux_client_read_packet: read header failed: Broken pipe\r\ndebug2: Received exit status from master 1\r\nShared connection to host.domain closed.\r\n')
<host.domain> ESTABLISH SSH CONNECTION FOR USER: None
<host.domain> SSH: EXEC ssh -vvv -C -o ControlMaster=auto -o ControlPersist=60s -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o ConnectTimeout=10 -o ControlPath=/home/user/.ansible/cp/5a62727ccc host.domain '/bin/sh -c '"'"'rm -f -r /home/user/.ansible/tmp/ansible-tmp-1535354913.52-175980622514910/ > /dev/null 2>&1 && sleep 0'"'"''
<host.domain> (0, '', 'OpenSSH_7.6p1 Ubuntu-4, OpenSSL 1.0.2n  7 Dec 2017\r\ndebug1: Reading configuration data /home/user/.ssh/config\r\ndebug1: Reading configuration data /etc/ssh/ssh_config\r\ndebug1: /etc/ssh/ssh_config line 19: Applying options for *\r\ndebug1: auto-mux: Trying existing master\r\ndebug2: fd 3 setting O_NONBLOCK\r\ndebug2: mux_client_hello_exchange: master version 4\r\ndebug3: mux_client_forwards: request forwardings: 0 local, 0 remote\r\ndebug3: mux_client_request_session: entering\r\ndebug3: mux_client_request_alive: entering\r\ndebug3: mux_client_request_alive: done pid = 2991\r\ndebug3: mux_client_request_session: session request sent\r\ndebug1: mux_client_request_session: master session id: 2\r\ndebug3: mux_client_read_packet: read header failed: Broken pipe\r\ndebug2: Received exit status from master 0\r\n')
failed: [host.domain] (item=en_US.UTF-8) => {
    "changed": false,
    "invocation": {
        "module_args": {
            "name": "en_US.UTF-8",
            "state": "present"
        }
    },
    "item": "en_US.UTF-8",
    "msg": "/etc/locale.gen and /var/lib/locales/supported.d/local are missing. Is the package \"locales\" installed?"
}
```


### Code of Conduct

- [X] I agree to follow the Ansible Code of Conduct


Discussion:
Files identified in the description:
* [`plugins/modules/system/locale_gen.py`](https://github.com/ansible-collections/community.general/blob/main/plugins/modules/system/locale_gen.py)

If these files are incorrect, please update the `component name` section of the description or use the `!component` bot command.

[click here for bot help](https://github.com/ansible/ansibullbot/blob/devel/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
cc @AugustusKling
[click here for bot help](https://github.com/ansible/ansibullbot/blob/devel/ISSUE_HELP.md)
<!--- boilerplate: notify --->
This comment from the linked issue seems to explain the situation pretty well: https://github.com/ansible/ansible/issues/44708#issuecomment-575477809
I agree but I still think that there is room for improvement.

- updating documentation could be an option - I have loads of debian/rhel (the curse of mixed infra :) ) playbooks and this is the only module that is not compatible with both (besides the apt / yum ones of course). If agreed I could try my hand opening a PR for this
- or the error message needs updating - it could check and if on a rhel distro it should let the user know it's unsupported instead of recommending an nonexistent package. 
Updating the documentation tends to be a lot easier than adding a distribution check (also there are lots of other distributions, some might have this and some might not).
Files identified in the description:
* [`plugins/modules/locale_gen.py`](https://github.com/ansible-collections/community.general/blob/main/plugins/modules/locale_gen.py)

If these files are incorrect, please update the `component name` section of the description or use the `!component` bot command.

[click here for bot help](https://github.com/ansible/ansibullbot/blob/devel/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
I want to update the documentation. Any ideas about what exactly to write? I would have gone with an additional note:

```
This module is not supported on RHEL-based systems.
```
I would rather formulate this as `This module does not support RHEL-based systems.` But then, I'm not a native English speaker, so... :) @samccann @oraNod, which formulation (@ziegenberg's or mine) do you think is better?
Hi @felixfontein, I take from your comment, that the general direction of my proposed change is right. :)
And I like your version more than mine, because you use active voice and mine has passive voice. I'll prepare a PR.

Resolving patch:
diff --git a/plugins/modules/locale_gen.py b/plugins/modules/locale_gen.py
--- a/plugins/modules/locale_gen.py
+++ b/plugins/modules/locale_gen.py
@@ -35,6 +35,8 @@
             - Whether the locale shall be present.
         choices: [ absent, present ]
         default: present
+notes:
+    - This module does not support RHEL-based systems.
 '''
 
 EXAMPLES = '''
@@ -74,11 +76,10 @@ def is_available(name, ubuntuMode):
     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*(?P<locale>\S+[\._\S]*) (?P<charset>\S+)\s*$'
     if ubuntuMode:
-        __regexp = r'^(?P<locale>\S+_\S+) (?P<charset>\S+)\s*$'
         __locales_available = '/usr/share/i18n/SUPPORTED'
     else:
-        __regexp = r'^#{0,1}\s*(?P<locale>\S+_\S+) (?P<charset>\S+)\s*$'
         __locales_available = '/etc/locale.gen'
 
     re_compiled = re.compile(__regexp)
@@ -88,7 +89,8 @@ def is_available(name, ubuntuMode):
         if result and result.group('locale') == name:
             return True
     fd.close()
-    return False
+    # locale may be installed but not listed in the file, for example C.UTF-8 in some systems
+    return is_present(name)
 
 
 def is_present(name):
@@ -106,20 +108,6 @@ def fix_case(name):
     return name
 
 
-def replace_line(existing_line, new_line):
-    """Replaces lines in /etc/locale.gen"""
-    try:
-        f = open("/etc/locale.gen", "r")
-        lines = [line.replace(existing_line, new_line) for line in f]
-    finally:
-        f.close()
-    try:
-        f = open("/etc/locale.gen", "w")
-        f.write("".join(lines))
-    finally:
-        f.close()
-
-
 def set_locale(name, enabled=True):
     """ Sets the state of the locale. Defaults to enabled. """
     search_string = r'#{0,1}\s*%s (?P<charset>.+)' % name
@@ -209,7 +197,7 @@ def main():
             # We found the common way to manage locales.
             ubuntuMode = False
         else:
-            module.fail_json(msg="/etc/locale.gen and /var/lib/locales/supported.d/local are missing. Is the package \"locales\" installed?")
+            module.fail_json(msg="/etc/locale.gen and /var/lib/locales/supported.d/local are missing. Is the package 'locales' installed?")
     else:
         # Ubuntu created its own system to manage locales.
         ubuntuMode = True


Test patch:
diff --git a/tests/integration/targets/locale_gen/aliases b/tests/integration/targets/locale_gen/aliases
--- a/tests/integration/targets/locale_gen/aliases
+++ b/tests/integration/targets/locale_gen/aliases
@@ -6,3 +6,5 @@ azp/posix/3
 destructive
 needs/root
 skip/aix
+skip/freebsd
+skip/macos
diff --git a/tests/integration/targets/locale_gen/tasks/basic.yml b/tests/integration/targets/locale_gen/tasks/basic.yml
new file mode 100644
--- /dev/null
+++ b/tests/integration/targets/locale_gen/tasks/basic.yml
@@ -0,0 +1,102 @@
+---
+# 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? {{ locale_basic.localegen }}
+  command: locale -a
+  register: initial_state
+  ignore_errors: true
+
+- name: Make sure the locale is not installed {{ locale_basic.localegen }}
+  locale_gen:
+    name: "{{ locale_basic.localegen }}"
+    state: absent
+
+- name: Is the locale present? {{ locale_basic.localegen }}
+  command: locale -a
+  register: cleaned
+  ignore_errors: true
+
+- name: Make sure the locale is not present {{ locale_basic.localegen }}
+  assert:
+    that:
+      - locale_basic.skip_removal or locale_basic.locales | intersect(cleaned.stdout_lines) == []
+
+- name: Install the locale {{ locale_basic.localegen }}
+  locale_gen:
+    name: "{{ locale_basic.localegen }}"
+    state: present
+  register: output_present
+
+- name: Is the locale present? {{ locale_basic.localegen }}
+  command: locale -a
+  register: post_check_output_present
+  ignore_errors: true
+
+- name: Make sure the locale is present and we say we installed it {{ locale_basic.localegen }}
+  assert:
+    that:
+      - locale_basic.locales | intersect(post_check_output_present.stdout_lines) != []
+      - locale_basic.skip_removal or output_present is changed
+
+- name: Install the locale a second time {{ locale_basic.localegen }}
+  locale_gen:
+    name: "{{ locale_basic.localegen }}"
+    state: present
+  register: output_present_idempotent
+
+- name: Is the locale present? {{ locale_basic.localegen }}
+  command: locale -a
+  register: post_check_output_present_idempotent
+  ignore_errors: true
+
+- name: Make sure the locale is present and we reported no change {{ locale_basic.localegen }}
+  assert:
+    that:
+      - locale_basic.locales | intersect(post_check_output_present_idempotent.stdout_lines) != []
+      - output_present_idempotent is not changed
+
+- name: Removals
+  when: locale_basic.skip_removal is false
+  block:
+    - name: Remove the locale {{ locale_basic.localegen }}
+      locale_gen:
+        name: "{{ locale_basic.localegen }}"
+        state: absent
+      register: output_absent
+
+    - name: Is the locale present? {{ locale_basic.localegen }}
+      command: locale -a
+      register: post_check_output_absent
+      ignore_errors: true
+
+    - name: Make sure the locale is absent and we reported a change {{ locale_basic.localegen }}
+      assert:
+        that:
+          - locale_basic.locales | intersect(post_check_output_absent.stdout_lines) == []
+          - output_absent is changed
+
+    - name: Remove the locale a second time {{ locale_basic.localegen }}
+      locale_gen:
+        name: "{{ locale_basic.localegen }}"
+        state: absent
+      register: output_absent_idempotent
+
+    - name: Is the locale present? {{ locale_basic.localegen }}
+      command: locale -a
+      register: post_check_output_absent_idempotent
+      ignore_errors: true
+
+    - name: Make sure the locale is absent and we reported no change {{ locale_basic.localegen }}
+      assert:
+        that:
+          - locale_basic.locales | intersect(post_check_output_absent_idempotent.stdout_lines) == []
+          - output_absent_idempotent is not changed
+
+# Cleanup
+- name: Reinstall the locale we tested against if it was initially installed {{ locale_basic.localegen }}
+  locale_gen:
+    name: "{{ locale_basic.localegen }}"
+    state: present
+  when: locale_basic.locales | intersect(initial_state.stdout_lines) != []
diff --git a/tests/integration/targets/locale_gen/tasks/locale_gen.yml b/tests/integration/targets/locale_gen/tasks/locale_gen.yml
deleted file mode 100644
--- a/tests/integration/targets/locale_gen/tasks/locale_gen.yml
+++ /dev/null
@@ -1,99 +0,0 @@
----
-# 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?
-  shell: locale -a | grep pt_BR
-  register: initial_state
-  ignore_errors: true
-
-- name: Make sure the locale is not installed
-  locale_gen:
-    name: pt_BR
-    state: absent
-
-- name: Is the locale present?
-  shell: locale -a | grep pt_BR
-  register: cleaned
-  ignore_errors: true
-
-- name: Make sure the locale is not present
-  assert:
-    that:
-      - "cleaned.rc == 1"
-
-- name: Install the locale
-  locale_gen:
-    name: pt_BR
-    state: present
-  register: output
-
-- name: Is the locale present?
-  shell: locale -a | grep pt_BR
-  register: post_check_output
-  ignore_errors: true
-
-- name: Make sure the locale is present and we say we installed it
-  assert:
-    that:
-      - "post_check_output.rc == 0"
-      - "output.changed"
-
-- name: Install the locale a second time
-  locale_gen:
-    name: pt_BR
-    state: present
-  register: output
-
-- name: Is the locale present?
-  shell: locale -a | grep pt_BR
-  register: post_check_output
-  ignore_errors: true
-
-- name: Make sure the locale is present and we reported no change
-  assert:
-    that:
-      - "post_check_output.rc == 0"
-      - "not output.changed"
-
-- name: Remove the locale
-  locale_gen:
-    name: pt_BR
-    state: absent
-  register: output
-
-- name: Is the locale present?
-  shell: locale -a | grep pt_BR
-  register: post_check_output
-  ignore_errors: true
-
-- name: Make sure the locale is absent and we reported a change
-  assert:
-    that:
-      - "post_check_output.rc == 1"
-      - "output.changed"
-
-- name: Remove the locale a second time
-  locale_gen:
-    name: pt_BR
-    state: absent
-  register: output
-
-- name: Is the locale present?
-  shell: locale -a | grep pt_BR
-  register: post_check_output
-  ignore_errors: true
-
-- name: Make sure the locale is absent and we reported no change
-  assert:
-    that:
-      - "post_check_output.rc == 1"
-      - "not output.changed"
-
-# Cleanup
-- name: Reinstall the locale we tested against if it was initially installed
-  locale_gen:
-    name: pt_BR
-    state: present
-  when: initial_state.rc == 0
diff --git a/tests/integration/targets/locale_gen/tasks/main.yml b/tests/integration/targets/locale_gen/tasks/main.yml
--- a/tests/integration/targets/locale_gen/tasks/main.yml
+++ b/tests/integration/targets/locale_gen/tasks/main.yml
@@ -8,5 +8,11 @@
 # 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
 
-- include_tasks: 'locale_gen.yml'
-  when: ansible_distribution in ('Ubuntu', 'Debian')
+- name: Bail out if not supported
+  ansible.builtin.meta: end_play
+  when: ansible_distribution not in ('Ubuntu', 'Debian')
+
+- include_tasks: basic.yml
+  loop: "{{ locale_list_basic }}"
+  loop_control:
+    loop_var: locale_basic
diff --git a/tests/integration/targets/locale_gen/vars/main.yml b/tests/integration/targets/locale_gen/vars/main.yml
new file mode 100644
--- /dev/null
+++ b/tests/integration/targets/locale_gen/vars/main.yml
@@ -0,0 +1,17 @@
+---
+# 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
+
+# locale_basic: pt_BR
+
+locale_list_basic:
+  - localegen: pt_BR
+    locales: [pt_BR]
+    skip_removal: false
+  - localegen: C.UTF-8
+    locales: [C.utf8, C.UTF-8]
+    skip_removal: true
+  - localegen: eo
+    locales: [eo]
+    skip_removal: false