16

I am looking for an idempotent ansible role/task to ensure a certain locale (like en_US.UTF-8) is set as default. It should generate the locale (only if necessary) and set it as a default (also only if necessary).

For the first part I'd register the output of locale -a | grep "{{ locale_name }}" and generate if needed.

For the second part I'm wondering if running update-locale every time is good enough because that command itself is idempotent anyway.

mniess
  • 343
  • 1
  • 2
  • 9
  • 2
    For the first part see [`locale_gen`](https://docs.ansible.com/ansible/latest/modules/locale_gen_module.html). For the second, what is update-locale? That's not the way to set the locale on a modern Linux system (and I can't even find it!), `localectl` is. – Michael Hampton Mar 19 '19 at 22:42
  • `update-locale` is the command that works on all Debian-family systems. It does the same as localectl and the question remains the same (for the second part). :) – mniess Apr 02 '19 at 17:13

4 Answers4

16

The localectl command from @mniess answer will always report as "changed" even if the new values are the equal to the current ones. Here's what I personally ended up to set both LANG and LANGUAGE and solve that issue:

- name: Ensure localisation files for '{{ config_system_locale }}' are available
  locale_gen:
    name: "{{ config_system_locale }}"
    state: present

- name: Ensure localisation files for '{{ config_system_language }}' are available
  locale_gen:
    name: "{{ config_system_language }}"
    state: present

- name: Get current locale and language configuration
  command: localectl status
  register: locale_status
  changed_when: false

- name: Parse 'LANG' from current locale and language configuration
  set_fact:
    locale_lang: "{{ locale_status.stdout | regex_search('LANG=([^\n]+)', '\\1') | first }}"

- name: Parse 'LANGUAGE' from current locale and language configuration
  set_fact:
    locale_language: "{{ locale_status.stdout | regex_search('LANGUAGE=([^\n]+)', '\\1') | default([locale_lang], true) | first }}"

- name: Configure locale to '{{ config_system_locale }}' and language to '{{ config_system_language }}'
  become: yes
  command: localectl set-locale LANG={{ config_system_locale }} LANGUAGE={{ config_system_language }}
  changed_when: locale_lang != config_system_locale or locale_language != config_system_language

Also, this what I have at group_vars/main.yml:

config_system_locale: 'pt_PT.UTF-8'
config_system_language: 'en_US.UTF-8'
Markus
  • 103
  • 4
rfgamaral
  • 960
  • 2
  • 11
  • 18
8

Here is what I ended up with:

- name: Ensure the locale exists
  locale_gen:
    name: en_US.UTF-8
    state: present
- name: set as default locale
  command: localectl set-locale LANG=en_US.UTF-8 LC_ALL=en_US.UTF-8
mniess
  • 343
  • 1
  • 2
  • 9
  • Note: At least on Ubuntu setting `LC_ALL` with the `localectl set-locale` command fails with the following error: `Failed to issue method call: Invalid Locale data.` – ToFi Mar 08 '22 at 10:10
  • @ToFi locale data needs to be installed for this to work (e.g. language-pack-*). – mniess Mar 09 '22 at 15:59
  • @mniess I install `language-pack-*` and it din't work, it only works if I removed the `LC_ALL=en_US.UTF-8` – JuanPablo Jan 14 '23 at 16:00
2

While rfgamaral's answer is great, it only works in Debian because the locale_gen community module does not support RHEL. To support both RHEL/CentOS and Debian, try the following:

- name: check if locale exists
  shell: "locale -a | grep -i {{ config_system_locale | regex_replace('-', '') | quote }}"
  register: found_locale
  changed_when: no
  failed_when: no

- name: create locale
  command: "localedef -i {{ config_system_locale | regex_replace('(.*)\\..*', '\\1') | quote }} -f {{ config_system_locale | regex_replace('.*\\.(.*)', '\\1') | quote }} {{ config_system_locale | quote }}"
  when: not ansible_check_mode and found_locale.rc != 0

- name: check if language exists
  shell: "locale -a | grep -i {{ config_system_language | regex_replace('-', '') | quote }}"
  register: found_language
  changed_when: no
  failed_when: no

- name: create language
  command: "localedef -i {{ config_system_language | regex_replace('(.*)\\..*', '\\1') | quote }} -f {{ config_system_language | regex_replace('.*\\.(.*)', '\\1') | quote }} {{ config_system_language | quote }}"
  when: not ansible_check_mode and found_language.rc != 0

- name: Get current locale and language configuration
  command: localectl status
  register: locale_status
  changed_when: false

- name: Parse 'LANG' from current locale and language configuration
  set_fact:
    locale_lang: "{{ locale_status.stdout | regex_search('LANG=([^\n]+)', '\\1') | first }}"

- name: Parse 'LANGUAGE' from current locale and language configuration
  set_fact:
    locale_language: "{{ locale_status.stdout | regex_search('LANGUAGE=([^\n]+)', '\\1') | default([locale_lang], true) | first }}"

- name: Configure locale to '{{ config_system_locale }}' and language to '{{ config_system_language }}'
  command: localectl set-locale LANG={{ config_system_locale }} LANGUAGE={{ config_system_language }}
  changed_when: locale_lang != config_system_locale or locale_language != config_system_language
Michael Altfield
  • 739
  • 2
  • 8
  • 23
0

I had the same problem on a fedora system and ended up with a solution that works exclusively with localectl:

playbook:

- name: Apply localectl settings
  tags:
      - system
      - localectl
  block:
      - name: Import localectl vars
        ansible.builtin.include_vars: localectl/vars.yml

      - name: Set localectl setting
        ansible.builtin.include_tasks: localectl/_tasks.yml
        loop: "{{ localectl_settings }}"
        loop_control:
            label: "{{ item.name }}"

localectl/vars.yml:

localectl_settings:
    - name: language
      value: LANG=de_DE.UTF-8
      regex: "System Locale: "
      command: set-locale

    - name: keymap
      value: euro
      regex: "VC Keymap: "
      command: set-keymap

    - name: x11 layout
      value: eu
      regex: "X11 Layout: "
      command: set-x11-keymap

localectl/_tasks.yml:

---
- name: Set localectl settings
  become: true
  tags:
      - system
      - localectl
  block:
      - name: Get status of localectl {{ item.name }}
        ansible.builtin.shell:
            cmd: localectl status | grep -E "{{ item.regex }}{{ item.value }}"
        register: result_locale_status
        failed_when: false
        changed_when: false

      - name: Set localectl setting
        ansible.builtin.command:
            cmd: "{{ localectl_command }}"
        when: result_locale_status.rc == 1
        loop_control:
            loop_var: localectl_command
        loop:
            - "localectl {{ item.command }} {{ item.value }}"

zkygr
  • 1
  • 1