--- setup.py.orig +++ setup.py @@ -26,6 +26,7 @@ import subprocess RENDERED_TMPD_PREFIX = "RENDERED_TEMPD" +VARIANT = None def is_f(p): return os.path.isfile(p) @@ -114,10 +115,20 @@ def render_tmpl(template): atexit.register(shutil.rmtree, tmpd) bname = os.path.basename(template).rstrip(tmpl_ext) fpath = os.path.join(tmpd, bname) - tiny_p([sys.executable, './tools/render-cloudcfg', template, fpath]) + if VARIANT: + tiny_p([sys.executable, './tools/render-cloudcfg', '--variant', + VARIANT, template, fpath]) + else: + tiny_p([sys.executable, './tools/render-cloudcfg', template, fpath]) # return path relative to setup.py return os.path.join(os.path.basename(tmpd), bname) +# User can set the variant for template rendering +if '--distro' in sys.argv: + idx = sys.argv.index('--distro') + VARIANT = sys.argv[idx+1] + del sys.argv[idx+1] + sys.argv.remove('--distro') INITSYS_FILES = { 'sysvinit': [f for f in glob('sysvinit/redhat/*') if is_f(f)], @@ -145,7 +156,6 @@ INITSYS_ROOTS = { } INITSYS_TYPES = sorted([f.partition(".")[0] for f in INITSYS_ROOTS.keys()]) - # Install everything in the right location and take care of Linux (default) and # FreeBSD systems. USR = "usr" @@ -158,6 +168,19 @@ if os.uname()[0] == 'FreeBSD': elif os.path.isfile('/etc/redhat-release'): USR_LIB_EXEC = "usr/libexec" +if VARIANT and sys.argv[1] == 'install': + base = ETC + config_dir = '/cloud/cloud.cfg.d' + if sys.argv.index('--root'): + root_idx = sys.argv.index('--root') + root_loc = sys.argv[root_idx+1] + base = root_loc + '/' + ETC + if not os.path.exists(base + config_dir): + os.makedirs(base + config_dir) + usr_distro = open(base + '/cloud/cloud.cfg.d/cloud-init.user.distro', 'w') + usr_distro.write(VARIANT) + usr_distro.close() + class MyEggInfo(egg_info): """This makes sure to not include the rendered files in SOURCES.txt.""" @@ -259,7 +282,7 @@ requirements = read_requires() setuptools.setup( name='cloud-init', version=get_version(), - description='EC2 initialisation magic', + description='Cloud initialisation magic', author='Scott Moser', author_email='scott.moser@canonical.com', url='http://launchpad.net/cloud-init/', --- cloudinit/tests/test_util.py.orig +++ cloudinit/tests/test_util.py @@ -3,6 +3,7 @@ """Tests for cloudinit.util""" import logging +import platform from textwrap import dedent import cloudinit.util as util @@ -16,6 +17,29 @@ MOUNT_INFO = [ '153 68 254:0 / /home rw,relatime shared:101 - xfs /dev/sda2 rw,attr2' ] +OS_RELEASE_SLES = dedent("""\ +NAME="SLES"\n +VERSION="12-SP3"\n +VERSION_ID="12.3"\n +PRETTY_NAME="SUSE Linux Enterprise Server 12 SP3"\n +ID="sles"\nANSI_COLOR="0;32"\n +CPE_NAME="cpe:/o:suse:sles:12:sp3"\n +""") + +OS_RELEASE_UBUNTU = dedent("""\ +NAME="Ubuntu"\n +VERSION="16.04.3 LTS (Xenial Xerus)"\n +ID=ubuntu\n +ID_LIKE=debian\n +PRETTY_NAME="Ubuntu 16.04.3 LTS"\n +VERSION_ID="16.04"\n +HOME_URL="http://www.ubuntu.com/"\n +SUPPORT_URL="http://help.ubuntu.com/"\n +BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"\n +VERSION_CODENAME=xenial\n +UBUNTU_CODENAME=xenial\n +""") + class FakeCloud(object): @@ -65,6 +89,54 @@ class TestUtil(CiTestCase): is_rw = util.mount_is_read_write('/') self.assertEqual(is_rw, False) + @mock.patch('os.path.exists') + @mock.patch('cloudinit.util.load_file') + def test_get_linux_distro_quoted_name(self, m_os_release, m_path_exists): + m_os_release.return_value = OS_RELEASE_SLES + m_path_exists.side_effect = os_release_exists + dist = util.get_linux_distro() + self.assertEqual(('sles', '12.3', platform.machine()), dist) + + @mock.patch('os.path.exists') + @mock.patch('cloudinit.util.load_file') + def test_get_linux_distro_bare_name(self, m_os_release, m_path_exists): + m_os_release.return_value = OS_RELEASE_UBUNTU + m_path_exists.side_effect = os_release_exists + dist = util.get_linux_distro() + self.assertEqual(('ubuntu', '16.04', platform.machine()), dist) + + @mock.patch('os.path.exists') + @mock.patch('platform.dist') + def test_get_linux_distro_no_data(self, m_platform_dist, m_path_exists): + m_platform_dist.return_value = ('', '', '') + m_path_exists.return_value = 0 + dist = util.get_linux_distro() + self.assertEqual(('', '', ''), dist) + + @mock.patch('os.path.exists') + @mock.patch('platform.dist') + def test_get_linux_distro_no_impl(self, m_platform_dist, m_path_exists): + m_platform_dist.side_effect = Exception() + m_path_exists.return_value = 0 + dist = util.get_linux_distro() + self.assertEqual(('', '', ''), dist) + + @mock.patch('os.path.exists') + @mock.patch('platform.dist') + def test_get_linux_distro_plat_data(self, m_platform_dist, m_path_exists): + m_platform_dist.return_value = ('foo', '1.1', 'aarch64') + m_path_exists.return_value = 0 + dist = util.get_linux_distro() + self.assertEqual(('foo', '1.1', 'aarch64'), dist) + + @mock.patch('os.path.exists') + @mock.patch('cloudinit.util.load_file') + def test_get_linux_distro_user_set(self, m_user_data, m_path_exists): + m_user_data.return_value = 'debian' + m_path_exists.side_effect = user_set_distro + dist = util.get_linux_distro() + self.assertEqual(('debian', 'not set', platform.machine()), dist) + class TestShellify(CiTestCase): @@ -212,4 +284,13 @@ class TestBlkid(CiTestCase): capture=True, decode="replace") +def os_release_exists(path): + if path == '/etc/os-release': + return 1 + + +def user_set_distro(path): + if path == '/etc/cloud/cloud.cfg.d/cloud-init.user.distro': + return 1 + # vi: ts=4 expandtab --- cloudinit/util.py.orig +++ cloudinit/util.py @@ -575,6 +575,43 @@ def get_cfg_option_str(yobj, key, defaul def get_cfg_option_int(yobj, key, default=0): return int(get_cfg_option_str(yobj, key, default=default)) +def get_linux_distro(): + distro_name = '' + distro_version = '' + if os.path.exists('/etc/cloud/cloud.cfg.d/cloud-init.user.distro'): + distro_name = load_file( + '/etc/cloud/cloud.cfg.d/cloud-init.user.distro') + distro_version = 'not set' + elif os.path.exists('/etc/os-release'): + os_release = load_file('/etc/os-release').split('\n') + for entry in os_release: + if entry.startswith('ID='): + distro_name = entry.split('=')[-1] + if '"' in distro_name: + distro_name = distro_name.split('"')[1] + if entry.startswith('VERSION_ID='): + # Lets hope for the best that distros stay consistent ;) + distro_version = entry.split('"')[1] + else: + dist = ('', '', '') + try: + # Will be removed in 3.7 + dist = platform.dist() # pylint: disable=W1505 + except Exception: + pass + finally: + found = None + for entry in dist: + if entry: + found = 1 + if not found: + msg = 'Unable to determine distribution, template expansion ' + msg += 'may have unexpected results' + LOG.warning(msg) + return dist + + return (distro_name, distro_version, platform.machine()) + def system_info(): info = { @@ -583,19 +620,19 @@ def system_info(): 'release': platform.release(), 'python': platform.python_version(), 'uname': platform.uname(), - 'dist': platform.dist(), # pylint: disable=W1505 + 'dist': get_linux_distro() } system = info['system'].lower() var = 'unknown' if system == "linux": linux_dist = info['dist'][0].lower() - if linux_dist in ('centos', 'fedora', 'debian'): + if linux_dist in ('centos', 'fedora', 'debian', 'rhel', 'suse'): var = linux_dist elif linux_dist in ('ubuntu', 'linuxmint', 'mint'): var = 'ubuntu' elif linux_dist == 'redhat': var = 'rhel' - elif linux_dist == 'suse': + elif linux_dist in ('opensuse', 'sles'): var = 'suse' else: var = 'linux'