Ryan Harper has proposed merging ~raharper/cloud-init:default-lang-c-utf8 into cloud-init:master.
Requested reviews: cloud-init commiters (cloud-init-dev) For more details, see: https://code.launchpad.net/~raharper/cloud-init/+git/cloud-init/+merge/329152 distro: allow distro to specify a default locale Currently the cloud-init default locale (en_US.UTF-8) is set by the base datasource class. This patch allows a distro to overide the fallback value with one that's available in the distro. For Debian and Ubuntu, the C.UTF-8 lang is available and built-in to cloud-images. Adjust apply_locale logic to skip locale-regen if the specified LANG value is C.UTF-8, it does not require regeneration. Further add unittests to exercise the default paths for Ubuntu and non-ubuntu paths to validate they get the LANG expected. -- Your team cloud-init commiters is requested to review the proposed merge of ~raharper/cloud-init:default-lang-c-utf8 into cloud-init:master.
diff --git a/cloudinit/distros/__init__.py b/cloudinit/distros/__init__.py index 1fd48a7..ac4b2a4 100755 --- a/cloudinit/distros/__init__.py +++ b/cloudinit/distros/__init__.py @@ -188,6 +188,9 @@ class Distro(object): def _get_localhost_ip(self): return "127.0.0.1" + def get_locale(self): + raise NotImplementedError() + @abc.abstractmethod def _read_hostname(self, filename, default=None): raise NotImplementedError() diff --git a/cloudinit/distros/debian.py b/cloudinit/distros/debian.py index abfb81f..b2904e8 100644 --- a/cloudinit/distros/debian.py +++ b/cloudinit/distros/debian.py @@ -127,6 +127,10 @@ class Distro(distros.Distro): # Note: http://www.leonardoborda.com/blog/127-0-1-1-ubuntu-debian/ return "127.0.1.1" + def get_locale(self): + # Debian and Ubuntu include C.UTF-8 Lang support in their images + return 'C.UTF-8' + def set_timezone(self, tz): distros.set_etc_timezone(tz=tz, tz_file=self._find_tz_file(tz)) @@ -237,7 +241,7 @@ def apply_locale(locale, sys_path=LOCALE_CONF_FN, keyname='LANG'): if os.path.exists(sys_path): locale_content = util.load_file(sys_path) - # if LANG isn't present, regen + # if LANG is set and matches locale, no need to regenerate sys_defaults = util.load_shell_content(locale_content) sys_val = sys_defaults.get(keyname, "") if sys_val.lower() == locale.lower(): @@ -246,9 +250,17 @@ def apply_locale(locale, sys_path=LOCALE_CONF_FN, keyname='LANG'): keyname, sys_val, locale) return - util.subp(['locale-gen', locale], capture=False) + # Update system configuration (not found, or not the same) util.subp( ['update-locale', '--locale-file=' + sys_path, '%s=%s' % (keyname, locale)], capture=False) + # special case for C.UTF-8, it does not need to be regenerated + if locale.lower() == 'c.utf-8': + return + + # finally, trigger regeneration + util.subp(['locale-gen', locale], capture=False) + + # vi: ts=4 expandtab diff --git a/cloudinit/sources/__init__.py b/cloudinit/sources/__init__.py index 952caf3..42634e7 100644 --- a/cloudinit/sources/__init__.py +++ b/cloudinit/sources/__init__.py @@ -150,7 +150,15 @@ class DataSource(object): return None def get_locale(self): - return 'en_US.UTF-8' + """Default locale is en_US.UTF-8, but allow distros to override""" + locale = 'en_US.UTF-8' + try: + print('asking distro for locale') + locale = self.distro.get_locale() + except NotImplementedError: + print('not implemented, using cloud-init default') + pass + return locale @property def availability_zone(self): diff --git a/tests/unittests/test_distros/test_debian.py b/tests/unittests/test_distros/test_debian.py index 2330ad5..8096694 100644 --- a/tests/unittests/test_distros/test_debian.py +++ b/tests/unittests/test_distros/test_debian.py @@ -17,6 +17,16 @@ class TestDebianApplyLocale(CiTestCase): apply_locale(locale, sys_path=spath) m_subp.assert_not_called() + def test_no_regen_on_c_utf8(self, m_subp): + """If locale is set to C.UTF8, do not attempt to call locale-gen""" + spath = self.tmp_path("default-locale") + m_subp.return_value = (None, None) + locale = 'C.UTF-8' + apply_locale(locale, sys_path=spath) + self.assertEqual( + [['update-locale', '--locale-file=' + spath, 'LANG=%s' % locale]], + [p[0][0] for p in m_subp.call_args_list]) + def test_rerun_if_different(self, m_subp): """If system has different locale, locale-gen should be called.""" spath = self.tmp_path("default-locale") @@ -25,8 +35,8 @@ class TestDebianApplyLocale(CiTestCase): util.write_file(spath, 'LANG=fr_FR.UTF-8', omode="w") apply_locale(locale, sys_path=spath) self.assertEqual( - [['locale-gen', locale], - ['update-locale', '--locale-file=' + spath, 'LANG=%s' % locale]], + [['update-locale', '--locale-file=' + spath, 'LANG=%s' % locale], + ['locale-gen', locale]], [p[0][0] for p in m_subp.call_args_list]) def test_rerun_if_no_file(self, m_subp): @@ -36,8 +46,8 @@ class TestDebianApplyLocale(CiTestCase): locale = 'en_US.UTF-8' apply_locale(locale, sys_path=spath) self.assertEqual( - [['locale-gen', locale], - ['update-locale', '--locale-file=' + spath, 'LANG=%s' % locale]], + [['update-locale', '--locale-file=' + spath, 'LANG=%s' % locale], + ['locale-gen', locale]], [p[0][0] for p in m_subp.call_args_list]) def test_rerun_on_unset_system_locale(self, m_subp): @@ -48,8 +58,8 @@ class TestDebianApplyLocale(CiTestCase): util.write_file(spath, 'LANG=', omode="w") apply_locale(locale, sys_path=spath) self.assertEqual( - [['locale-gen', locale], - ['update-locale', '--locale-file=' + spath, 'LANG=%s' % locale]], + [['update-locale', '--locale-file=' + spath, 'LANG=%s' % locale], + ['locale-gen', locale]], [p[0][0] for p in m_subp.call_args_list]) def test_rerun_on_mismatched_keys(self, m_subp): @@ -60,9 +70,8 @@ class TestDebianApplyLocale(CiTestCase): util.write_file(spath, 'LANG=', omode="w") apply_locale(locale, sys_path=spath, keyname='LC_ALL') self.assertEqual( - [['locale-gen', locale], - ['update-locale', '--locale-file=' + spath, - 'LC_ALL=%s' % locale]], + [['update-locale', '--locale-file=' + spath, 'LC_ALL=%s' % locale], + ['locale-gen', locale]], [p[0][0] for p in m_subp.call_args_list]) def test_falseish_locale_raises_valueerror(self, m_subp): diff --git a/tests/unittests/test_distros/test_generic.py b/tests/unittests/test_distros/test_generic.py index c9be277..1972a7a 100644 --- a/tests/unittests/test_distros/test_generic.py +++ b/tests/unittests/test_distros/test_generic.py @@ -228,5 +228,19 @@ class TestGenericDistro(helpers.FilesystemMockingTestCase): os.symlink('/', '/run/systemd/system') self.assertFalse(d.uses_systemd()) + def test_get_locale_ubuntu(self): + """Test ubuntu distro returns locale set to C.UTF-8""" + cls = distros.fetch("ubuntu") + d = cls("ubuntu", {}, None) + locale = d.get_locale() + self.assertEqual('C.UTF-8', locale) + + def test_get_locale_rhel(self): + """Test rhel distro returns NotImplementedError exception""" + cls = distros.fetch("rhel") + d = cls("rhel", {}, None) + with self.assertRaises(NotImplementedError): + d.get_locale() + # vi: ts=4 expandtab diff --git a/tests/unittests/test_handler/test_handler_locale.py b/tests/unittests/test_handler/test_handler_locale.py index e9a810c..3c860c5 100644 --- a/tests/unittests/test_handler/test_handler_locale.py +++ b/tests/unittests/test_handler/test_handler_locale.py @@ -20,6 +20,8 @@ from configobj import ConfigObj from six import BytesIO import logging +import mock +import os import shutil import tempfile @@ -27,6 +29,9 @@ LOG = logging.getLogger(__name__) class TestLocale(t_help.FilesystemMockingTestCase): + + with_logs = True + def setUp(self): super(TestLocale, self).setUp() self.new_root = tempfile.mkdtemp() @@ -54,4 +59,40 @@ class TestLocale(t_help.FilesystemMockingTestCase): n_cfg = ConfigObj(BytesIO(contents)) self.assertEqual({'RC_LANG': cfg['locale']}, dict(n_cfg)) + def test_set_locale_sles_default(self): + cfg = {} + cc = self._get_cloud('sles') + cc_locale.handle('cc_locale', cfg, cc, LOG, []) + + contents = util.load_file('/etc/sysconfig/language', decode=False) + n_cfg = ConfigObj(BytesIO(contents)) + self.assertEqual({'RC_LANG': 'en_US.UTF-8'}, dict(n_cfg)) + + def test_locale_update_config_if_different_than_default(self): + """Test cc_locale writes updates conf if different than default""" + locale_conf = os.path.join(self.new_root, "etc/default/locale") + util.write_file(locale_conf, 'LANG="en_US.UTF-8"\n') + cfg = {} + cc = self._get_cloud('ubuntu') + with mock.patch('cloudinit.distros.debian.util.subp') as m_subp: + with mock.patch('cloudinit.distros.debian.LOCALE_CONF_FN', + locale_conf): + cc_locale.handle('cc_locale', cfg, cc, LOG, []) + m_subp.assert_called_with(['update-locale', + '--locale-file=%s' % locale_conf, + 'LANG=C.UTF-8'], capture=False) + + def test_locale_rhel_defaults_en_us_utf8(self): + """Test cc_locale gets en_US.UTF-8 from distro get_locale fallback""" + cfg = {} + cc = self._get_cloud('rhel') + update_sysconfig = 'cloudinit.distros.rhel_util.update_sysconfig_file' + with mock.patch.object(cc.distro, 'uses_systemd') as m_use_sd: + m_use_sd.return_value = True + with mock.patch(update_sysconfig) as m_update_syscfg: + cc_locale.handle('cc_locale', cfg, cc, LOG, []) + m_update_syscfg.assert_called_with('/etc/locale.conf', + {'LANG': 'en_US.UTF-8'}) + + # vi: ts=4 expandtab
_______________________________________________ Mailing list: https://launchpad.net/~cloud-init-dev Post to : cloud-init-dev@lists.launchpad.net Unsubscribe : https://launchpad.net/~cloud-init-dev More help : https://help.launchpad.net/ListHelp