1#!/usr/local/bin/python3.8 2# -*- coding: utf-8 -*- 3 4# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) 5 6from __future__ import absolute_import, division, print_function 7__metaclass__ = type 8 9DOCUMENTATION = ''' 10--- 11module: locale_gen 12short_description: Creates or removes locales 13description: 14 - Manages locales by editing /etc/locale.gen and invoking locale-gen. 15author: 16- Augustus Kling (@AugustusKling) 17options: 18 name: 19 type: str 20 description: 21 - Name and encoding of the locale, such as "en_GB.UTF-8". 22 required: true 23 state: 24 type: str 25 description: 26 - Whether the locale shall be present. 27 choices: [ absent, present ] 28 default: present 29''' 30 31EXAMPLES = ''' 32- name: Ensure a locale exists 33 community.general.locale_gen: 34 name: de_CH.UTF-8 35 state: present 36''' 37 38import os 39import re 40from subprocess import Popen, PIPE, call 41 42from ansible.module_utils.basic import AnsibleModule 43from ansible.module_utils.common.text.converters import to_native 44 45LOCALE_NORMALIZATION = { 46 ".utf8": ".UTF-8", 47 ".eucjp": ".EUC-JP", 48 ".iso885915": ".ISO-8859-15", 49 ".cp1251": ".CP1251", 50 ".koi8r": ".KOI8-R", 51 ".armscii8": ".ARMSCII-8", 52 ".euckr": ".EUC-KR", 53 ".gbk": ".GBK", 54 ".gb18030": ".GB18030", 55 ".euctw": ".EUC-TW", 56} 57 58 59# =========================================== 60# location module specific support methods. 61# 62 63def is_available(name, ubuntuMode): 64 """Check if the given locale is available on the system. This is done by 65 checking either : 66 * if the locale is present in /etc/locales.gen 67 * or if the locale is present in /usr/share/i18n/SUPPORTED""" 68 if ubuntuMode: 69 __regexp = r'^(?P<locale>\S+_\S+) (?P<charset>\S+)\s*$' 70 __locales_available = '/usr/share/i18n/SUPPORTED' 71 else: 72 __regexp = r'^#{0,1}\s*(?P<locale>\S+_\S+) (?P<charset>\S+)\s*$' 73 __locales_available = '/etc/locale.gen' 74 75 re_compiled = re.compile(__regexp) 76 fd = open(__locales_available, 'r') 77 for line in fd: 78 result = re_compiled.match(line) 79 if result and result.group('locale') == name: 80 return True 81 fd.close() 82 return False 83 84 85def is_present(name): 86 """Checks if the given locale is currently installed.""" 87 output = Popen(["locale", "-a"], stdout=PIPE).communicate()[0] 88 output = to_native(output) 89 return any(fix_case(name) == fix_case(line) for line in output.splitlines()) 90 91 92def fix_case(name): 93 """locale -a might return the encoding in either lower or upper case. 94 Passing through this function makes them uniform for comparisons.""" 95 for s, r in LOCALE_NORMALIZATION.items(): 96 name = name.replace(s, r) 97 return name 98 99 100def replace_line(existing_line, new_line): 101 """Replaces lines in /etc/locale.gen""" 102 try: 103 f = open("/etc/locale.gen", "r") 104 lines = [line.replace(existing_line, new_line) for line in f] 105 finally: 106 f.close() 107 try: 108 f = open("/etc/locale.gen", "w") 109 f.write("".join(lines)) 110 finally: 111 f.close() 112 113 114def set_locale(name, enabled=True): 115 """ Sets the state of the locale. Defaults to enabled. """ 116 search_string = r'#{0,1}\s*%s (?P<charset>.+)' % name 117 if enabled: 118 new_string = r'%s \g<charset>' % (name) 119 else: 120 new_string = r'# %s \g<charset>' % (name) 121 try: 122 f = open("/etc/locale.gen", "r") 123 lines = [re.sub(search_string, new_string, line) for line in f] 124 finally: 125 f.close() 126 try: 127 f = open("/etc/locale.gen", "w") 128 f.write("".join(lines)) 129 finally: 130 f.close() 131 132 133def apply_change(targetState, name): 134 """Create or remove locale. 135 136 Keyword arguments: 137 targetState -- Desired state, either present or absent. 138 name -- Name including encoding such as de_CH.UTF-8. 139 """ 140 if targetState == "present": 141 # Create locale. 142 set_locale(name, enabled=True) 143 else: 144 # Delete locale. 145 set_locale(name, enabled=False) 146 147 localeGenExitValue = call("locale-gen") 148 if localeGenExitValue != 0: 149 raise EnvironmentError(localeGenExitValue, "locale.gen failed to execute, it returned " + str(localeGenExitValue)) 150 151 152def apply_change_ubuntu(targetState, name): 153 """Create or remove locale. 154 155 Keyword arguments: 156 targetState -- Desired state, either present or absent. 157 name -- Name including encoding such as de_CH.UTF-8. 158 """ 159 if targetState == "present": 160 # Create locale. 161 # Ubuntu's patched locale-gen automatically adds the new locale to /var/lib/locales/supported.d/local 162 localeGenExitValue = call(["locale-gen", name]) 163 else: 164 # Delete locale involves discarding the locale from /var/lib/locales/supported.d/local and regenerating all locales. 165 try: 166 f = open("/var/lib/locales/supported.d/local", "r") 167 content = f.readlines() 168 finally: 169 f.close() 170 try: 171 f = open("/var/lib/locales/supported.d/local", "w") 172 for line in content: 173 locale, charset = line.split(' ') 174 if locale != name: 175 f.write(line) 176 finally: 177 f.close() 178 # Purge locales and regenerate. 179 # Please provide a patch if you know how to avoid regenerating the locales to keep! 180 localeGenExitValue = call(["locale-gen", "--purge"]) 181 182 if localeGenExitValue != 0: 183 raise EnvironmentError(localeGenExitValue, "locale.gen failed to execute, it returned " + str(localeGenExitValue)) 184 185 186def main(): 187 module = AnsibleModule( 188 argument_spec=dict( 189 name=dict(type='str', required=True), 190 state=dict(type='str', default='present', choices=['absent', 'present']), 191 ), 192 supports_check_mode=True, 193 ) 194 195 name = module.params['name'] 196 state = module.params['state'] 197 198 if not os.path.exists("/etc/locale.gen"): 199 if os.path.exists("/var/lib/locales/supported.d/"): 200 # Ubuntu created its own system to manage locales. 201 ubuntuMode = True 202 else: 203 module.fail_json(msg="/etc/locale.gen and /var/lib/locales/supported.d/local are missing. Is the package \"locales\" installed?") 204 else: 205 # We found the common way to manage locales. 206 ubuntuMode = False 207 208 if not is_available(name, ubuntuMode): 209 module.fail_json(msg="The locale you've entered is not available " 210 "on your system.") 211 212 if is_present(name): 213 prev_state = "present" 214 else: 215 prev_state = "absent" 216 changed = (prev_state != state) 217 218 if module.check_mode: 219 module.exit_json(changed=changed) 220 else: 221 if changed: 222 try: 223 if ubuntuMode is False: 224 apply_change(state, name) 225 else: 226 apply_change_ubuntu(state, name) 227 except EnvironmentError as e: 228 module.fail_json(msg=to_native(e), exitValue=e.errno) 229 230 module.exit_json(name=name, changed=changed, msg="OK") 231 232 233if __name__ == '__main__': 234 main() 235