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