Alexsander de Souza has proposed merging ~alexsander-souza/curtin:sles_support into curtin:master.
Commit message: add SLES support Requested reviews: curtin developers (curtin-dev) For more details, see: https://code.launchpad.net/~alexsander-souza/curtin/+git/curtin/+merge/435914 -- Your team curtin developers is requested to review the proposed merge of ~alexsander-souza/curtin:sles_support into curtin:master.
diff --git a/curtin/block/deps.py b/curtin/block/deps.py index 5cb23e0..ec5fe83 100644 --- a/curtin/block/deps.py +++ b/curtin/block/deps.py @@ -94,6 +94,25 @@ def detect_required_packages_mapping(osfamily=DISTROS.debian): 'zfs': [], 'zpool': [], }, + DISTROS.suse: { + 'bcache': ['bcache-tools'], + 'btrfs': ['btrfsprogs'], + 'dm_crypt': ['cryptsetup'], + 'ext2': ['e2fsprogs'], + 'ext3': ['e2fsprogs'], + 'ext4': ['e2fsprogs'], + 'jfs': ['jfsutils'], + 'iscsi': [], + 'lvm_partition': ['lvm2'], + 'lvm_volgroup': ['lvm2'], + 'ntfs': [], + 'raid': ['mdadm'], + 'reiserfs': [], + 'xfs': ['xfsprogs'], + 'zfsroot': [], + 'zfs': [], + 'zpool': [], + }, } if osfamily not in distro_mapping: raise ValueError('No block package mapping for distro: %s' % osfamily) diff --git a/curtin/commands/curthooks.py b/curtin/commands/curthooks.py index 03a53b1..69833a2 100644 --- a/curtin/commands/curthooks.py +++ b/curtin/commands/curthooks.py @@ -1038,6 +1038,7 @@ def detect_and_handle_multipath(cfg, target, osfamily=DISTROS.debian): DEFAULT_MULTIPATH_PACKAGES = { DISTROS.debian: ['multipath-tools-boot'], DISTROS.redhat: ['device-mapper-multipath'], + DISTROS.suse: ['multipath-tools'], } if osfamily not in DEFAULT_MULTIPATH_PACKAGES: raise ValueError( @@ -1154,7 +1155,7 @@ def detect_and_handle_multipath(cfg, target, osfamily=DISTROS.debian): grub_cfg = os.path.sep.join( [target, '/etc/default/grub.d/50-curtin-multipath.cfg']) omode = 'w' - elif osfamily == DISTROS.redhat: + elif osfamily in [DISTROS.redhat, DISTROS.suse]: grub_cfg = os.path.sep.join([target, '/etc/default/grub']) omode = 'a' else: @@ -1199,7 +1200,7 @@ def detect_and_handle_multipath(cfg, target, osfamily=DISTROS.debian): # Initrams needs to be updated to include /etc/multipath.cfg # and /etc/multipath/bindings files. update_initramfs(target, all_kernels=True) - elif osfamily == DISTROS.redhat: + elif osfamily in [DISTROS.redhat, DISTROS.suse]: # Write out initramfs/dracut config for multipath dracut_conf_multipath = os.path.sep.join( [target, '/etc/dracut.conf.d/10-curtin-multipath.conf']) @@ -1308,6 +1309,12 @@ def install_missing_packages(cfg, target, osfamily=DISTROS.debian): # SecureBoot support if distro.has_pkg_available("shim-signed"): uefi_pkgs.append("shim-signed") + elif osfamily == DISTROS.suse: + uefi_pkgs.extend(['grub2', 'grub2-branding-SLE']) + arch = distro.get_architecture() + if arch == 'amd64': + arch = 'x86_64' + uefi_pkgs.append('grub2-%s-efi' % arch) else: raise ValueError('Unknown grub2 package list for distro: %s' % osfamily) @@ -1753,6 +1760,14 @@ def builtin_curthooks(cfg, target, state): description="setting up swap"): add_swap(cfg, target, state.get('fstab')) + if osfamily == DISTROS.suse: + # set cloud-init maas datasource for SuSE images + if cfg.get('cloudconfig'): + handle_cloudconfig( + cfg['cloudconfig'], + base_dir=paths.target_path(target, + 'etc/cloud/cloud.cfg.d')) + if osfamily == DISTROS.redhat: # set cloud-init maas datasource for centos images if cfg.get('cloudconfig'): diff --git a/curtin/commands/install_grub.py b/curtin/commands/install_grub.py index 79b6695..38bf71a 100644 --- a/curtin/commands/install_grub.py +++ b/curtin/commands/install_grub.py @@ -250,7 +250,7 @@ def get_grub_install_command(uefi, distroinfo, target): # prefer grub-multi-install if present if uefi and os.path.exists(target_path(target, GRUB_MULTI_INSTALL)): grub_install_cmd = GRUB_MULTI_INSTALL - elif distroinfo.family == distro.DISTROS.redhat: + elif distroinfo.family in [distro.DISTROS.redhat, distro.DISTROS.suse]: grub_install_cmd = 'grub2-install' LOG.debug('Using grub install command: %s', grub_install_cmd) @@ -289,6 +289,23 @@ def gen_uefi_install_commands(grub_name, grub_target, grub_cmd, update_nvram, '/boot/efi/EFI/%s/grub.cfg' % bootid]) else: post_cmds.append(['grub2-mkconfig', '-o', '/boot/grub2/grub.cfg']) + elif distroinfo.family == distro.DISTROS.suse: + bootid = 'suse' + grub_cfg = '/boot/grub2/grub.cfg' + loader = find_efi_loader(target, bootid) + if loader: + grub_cmd = None + grub_cfg = '/boot/efi/EFI/%s/grub.cfg' % bootid + if update_nvram: + efi_disk, efi_part_num = get_efi_disk_part(devices) + # Add entry to the EFI boot menu + install_cmds.append(['efibootmgr', '--create', + '--write-signature', '--label', bootid, + '--disk', efi_disk, + '--part', efi_part_num, + '--loader', + efi_loader_esp_path(loader)]) + post_cmds.append(['grub2-mkconfig', '-o', grub_cfg]) else: raise ValueError("Unsupported os family for grub " "install: %s" % distroinfo.family) @@ -315,6 +332,8 @@ def gen_install_commands(grub_name, grub_cmd, distroinfo, devices, if distroinfo.family == distro.DISTROS.debian: install_cmds.append(['dpkg-reconfigure', grub_name]) install_cmds.append(['update-grub']) + elif distroinfo.family == distro.DISTROS.suse: + post_cmds.append(['grub2-mkconfig', '-o', '/boot/grub2/grub.cfg']) elif distroinfo.family == distro.DISTROS.redhat: if rhel_ver in ["7", "8", "9"]: post_cmds.append( diff --git a/curtin/distro.py b/curtin/distro.py index 618af47..4266278 100644 --- a/curtin/distro.py +++ b/curtin/distro.py @@ -379,6 +379,26 @@ def rpm_get_dist_id(target=None): return dist.rstrip() +def run_zypper_command(mode, args=None, opts=None, env=None, target=None, + execute=True, allow_daemons=False): + defopts = ['--non-interactive', '--non-interactive-include-reboot-patches', + '--quiet'] + + if args is None: + args = [] + + if opts is None: + opts = [] + + cmd = ['zypper'] + cmd += defopts + opts + [mode] + args + if not execute: + return env, cmd + + with ChrootableTarget(target, allow_daemons=allow_daemons) as inchroot: + return inchroot.subp(cmd, env=env) + + def system_upgrade(opts=None, target=None, env=None, allow_daemons=False, osfamily=None): LOG.debug("Upgrading system in %s", target) @@ -391,6 +411,8 @@ def system_upgrade(opts=None, target=None, env=None, allow_daemons=False, 'subcommands': ('dist-upgrade', 'autoremove')}, DISTROS.redhat: {'function': run_yum_command, 'subcommands': ('upgrade',)}, + DISTROS.suse: {'function': run_zypper_command, + 'subcommands': ('refresh', 'update', 'purge-kernels',)}, } if osfamily not in distro_cfg: raise ValueError('Distro "%s" does not have system_upgrade support', @@ -414,6 +436,7 @@ def install_packages(pkglist, osfamily=None, opts=None, target=None, env=None, installer_map = { DISTROS.debian: run_apt_command, DISTROS.redhat: run_yum_command, + DISTROS.suse: run_zypper_command, } install_cmd = installer_map.get(osfamily) @@ -429,10 +452,15 @@ def has_pkg_available(pkg, target=None, osfamily=None): if not osfamily: osfamily = get_osfamily(target=target) - if osfamily not in [DISTROS.debian, DISTROS.redhat]: + if osfamily not in [DISTROS.debian, DISTROS.redhat, DISTROS.suse]: raise ValueError('has_pkg_available: unsupported distro family: %s', osfamily) + if osfamily == DISTROS.suse: + out, _ = subp(['zypper', '--quiet', 'search', + '--match-exact', pkg], capture=True, target=target) + return 'No matching items found.' not in out + if osfamily == DISTROS.debian: out, _ = subp(['apt-cache', 'pkgnames'], capture=True, target=target) for item in out.splitlines(): @@ -603,7 +631,7 @@ def get_architecture(target=None, osfamily=None): if osfamily == DISTROS.debian: return dpkg_get_architecture(target=target) - if osfamily == DISTROS.redhat: + if osfamily in [DISTROS.redhat, DISTROS.suse]: return rpm_get_architecture(target=target) raise ValueError("Unhandled osfamily=%s" % osfamily) diff --git a/curtin/net/deps.py b/curtin/net/deps.py index b78654d..e019a9e 100644 --- a/curtin/net/deps.py +++ b/curtin/net/deps.py @@ -75,6 +75,14 @@ def detect_required_packages_mapping(osfamily=DISTROS.debian): 'openvswitch': ['openvswitch-switch'], 'vlan': [], 'vlans': []}, + DISTROS.suse: { + 'bond': [], + 'bonds': [], + 'bridge': ['bridge-utils'], + 'bridges': ['bridge-utils'], + 'openvswitch': ['openvswitch-switch'], + 'vlan': ['vlan'], + 'vlans': ['vlan']}, } if osfamily not in distro_mapping: raise ValueError('No net package mapping for distro: %s' % osfamily) diff --git a/tests/unittests/test_commands_install_grub.py b/tests/unittests/test_commands_install_grub.py index 24f9476..ab52505 100644 --- a/tests/unittests/test_commands_install_grub.py +++ b/tests/unittests/test_commands_install_grub.py @@ -830,6 +830,121 @@ class TestGenUefiInstallCommands(CiTestCase): grub_name, grub_target, grub_cmd, update_nvram, distroinfo, devices, self.target)) + def test_suse_install(self): + self.m_os_release.return_value = {'ID': 'suse'} + distroinfo = install_grub.distro.get_distroinfo() + grub_name = 'grub2-efi-x64' + grub_target = 'x86_64-efi' + grub_cmd = 'grub2-install' + update_nvram = True + devices = ['/dev/disk-a-part1'] + disk = '/dev/disk-a' + part = '1' + self.m_get_disk_part.return_value = (disk, part) + + expected_install = [ + ['efibootmgr', '-v'], + [grub_cmd, '--target=%s' % grub_target, + '--efi-directory=/boot/efi', + '--bootloader-id=suse', '--recheck'], + ] + expected_post = [ + ['grub2-mkconfig', '-o', '/boot/grub2/grub.cfg'], + ['efibootmgr', '-v'] + ] + self.assertEqual( + (expected_install, expected_post), + install_grub.gen_uefi_install_commands( + grub_name, grub_target, grub_cmd, update_nvram, distroinfo, + devices, self.target)) + + def test_suse_install_existing(self): + # simulate existing bootloaders already installed in target system + # by touching the files grub would have installed, including shim + def _enable_loaders(bootid): + efi_path = 'boot/efi/EFI' + target_efi_path = os.path.join(self.target, efi_path) + loaders = [ + os.path.join(target_efi_path, bootid, 'shimx64.efi'), + os.path.join(target_efi_path, 'BOOT', 'BOOTX64.EFI'), + os.path.join(target_efi_path, bootid, 'grubx64.efi'), + ] + for loader in loaders: + util.ensure_dir(os.path.dirname(loader)) + with open(loader, 'w+') as fh: + fh.write('\n') + + self.m_os_release.return_value = {'ID': 'suse'} + distroinfo = install_grub.distro.get_distroinfo() + _enable_loaders("suse") + grub_name = 'grub2-efi-x64' + grub_target = 'x86_64-efi' + grub_cmd = 'grub2-install' + update_nvram = True + devices = ['/dev/disk-a-part1'] + disk = '/dev/disk-a' + part = '1' + self.m_get_disk_part.return_value = (disk, part) + + expected_loader = '/EFI/suse/shimx64.efi' + expected_install = [ + ['efibootmgr', '-v'], + ['efibootmgr', '--create', '--write-signature', + '--label', 'suse', '--disk', disk, '--part', part, + '--loader', expected_loader], + ] + expected_post = [ + ['grub2-mkconfig', '-o', '/boot/efi/EFI/suse/grub.cfg'], + ['efibootmgr', '-v'] + ] + + self.assertEqual( + (expected_install, expected_post), + install_grub.gen_uefi_install_commands( + grub_name, grub_target, grub_cmd, update_nvram, distroinfo, + devices, self.target)) + + def test_suse_install_existing_no_nvram(self): + # verify grub install command is not executed if update_nvram is False + # on suse. + def _enable_loaders(bootid): + efi_path = 'boot/efi/EFI' + target_efi_path = os.path.join(self.target, efi_path) + loaders = [ + os.path.join(target_efi_path, bootid, 'shimx64.efi'), + os.path.join(target_efi_path, 'BOOT', 'BOOTX64.EFI'), + os.path.join(target_efi_path, bootid, 'grubx64.efi'), + ] + for loader in loaders: + util.write_file(loader, content="") + + self.m_os_release.return_value = {'ID': 'suse'} + distroinfo = install_grub.distro.get_distroinfo() + bootid = distroinfo.variant + _enable_loaders(bootid) + grub_name = 'grub2-efi-x64' + grub_target = 'x86_64-efi' + grub_cmd = 'grub2-install' + update_nvram = False + devices = ['/dev/disk-a-part1'] + disk = '/dev/disk-a' + part = '1' + self.m_get_disk_part.return_value = (disk, part) + + expected_install = [ + ['efibootmgr', '-v'], + ] + expected_post = [ + ['grub2-mkconfig', '-o', '/boot/efi/EFI/%s/grub.cfg' % bootid], + ['efibootmgr', '-v'] + ] + + self.assertEqual( + (expected_install, expected_post), + install_grub.gen_uefi_install_commands( + grub_name, grub_target, grub_cmd, update_nvram, distroinfo, + devices, self.target)) + class TestGenInstallCommands(CiTestCase): diff --git a/tests/unittests/test_curthooks.py b/tests/unittests/test_curthooks.py index a224819..c13434c 100644 --- a/tests/unittests/test_curthooks.py +++ b/tests/unittests/test_curthooks.py @@ -479,6 +479,22 @@ class TestInstallMissingPkgs(CiTestCase): expected_pkgs, target=target, osfamily=self.distro_family) @patch.object(events, 'ReportEventStack') + def test_install_packages_on_uefi_amd64_sles(self, mock_events): + arch = 'amd64' + self.mock_arch.return_value = arch + self.mock_machine.return_value = 'x86_64' + expected_pkgs = ['efibootmgr', 'grub2', 'grub2-branding-SLE', + 'grub2-x86_64-efi'] + self.mock_uefi.return_value = True + self.mock_haspkg.return_value = True + target = "not-a-real-target" + cfg = {} + curthooks.install_missing_packages( + cfg, target=target, osfamily=distro.DISTROS.suse) + self.mock_install_packages.assert_called_with( + expected_pkgs, target=target, osfamily=distro.DISTROS.suse) + + @patch.object(events, 'ReportEventStack') def test_install_packages_on_uefi_amd64_centos(self, mock_events): arch = 'amd64' self.mock_arch.return_value = arch diff --git a/tests/unittests/test_distro.py b/tests/unittests/test_distro.py index 7532126..06c25db 100644 --- a/tests/unittests/test_distro.py +++ b/tests/unittests/test_distro.py @@ -370,6 +370,34 @@ class TestYumInstall(CiTestCase): m_subp.assert_has_calls(expected_calls) +class TestZypperInstall(CiTestCase): + + @mock.patch.object(util.ChrootableTarget, "__enter__", new=lambda a: a) + @mock.patch('curtin.util.subp') + def test_zypper_install(self, m_subp): + pkglist = ['foobar', 'wark'] + target = 'mytarget' + expected_calls = [ + mock.call(['zypper', '--non-interactive', + '--non-interactive-include-reboot-patches', + '--quiet', 'install'] + pkglist, env=None, + target=paths.target_path(target)) + ] + + m_subp.reset_mock() + self.assertFalse(m_subp.called) + distro.run_zypper_command('install', pkglist, target=target) + m_subp.assert_has_calls(expected_calls) + + # call yum_install through install_packages; expect the same calls + # so clear m_subp's call stack. + m_subp.reset_mock() + self.assertFalse(m_subp.called) + osfamily = distro.DISTROS.suse + distro.install_packages(pkglist, osfamily=osfamily, target=target) + m_subp.assert_has_calls(expected_calls) + + class TestSystemUpgrade(CiTestCase): @mock.patch.object(util.ChrootableTarget, "__enter__", new=lambda a: a) @@ -469,6 +497,35 @@ class TestSystemUpgrade(CiTestCase): m_apt_update.assert_has_calls(apt_update_calls) m_subp.assert_has_calls(expected_calls) + @mock.patch.object(util.ChrootableTarget, "__enter__", new=lambda a: a) + @mock.patch('curtin.util.subp') + def test_system_upgrade_suse(self, m_subp): + """system_upgrade osfamily=suse + calls run_zypper_command mode=upgrade""" + osfamily = distro.DISTROS.suse + target = 'mytarget' + expected_calls = [ + mock.call(['zypper', '--non-interactive', + '--non-interactive-include-reboot-patches', '--quiet', + 'refresh'], env=None, + target='/work/curtin/mytarget'), + mock.call(['zypper', '--non-interactive', + '--non-interactive-include-reboot-patches', '--quiet', + 'update'], env=None, + target='/work/curtin/mytarget'), + mock.call(['zypper', '--non-interactive', + '--non-interactive-include-reboot-patches', '--quiet', + 'purge-kernels'], env=None, + target='/work/curtin/mytarget'), + ] + + # call system_upgrade via osfamily; note that we expect the same calls + # but to prevent a false positive we clear m_subp's call stack. + m_subp.reset_mock() + self.assertFalse(m_subp.called) + distro.system_upgrade(target=target, osfamily=osfamily) + m_subp.assert_has_calls(expected_calls) + class TestHasPkgAvailable(CiTestCase): @@ -519,6 +576,18 @@ class TestHasPkgAvailable(CiTestCase): self.assertEqual(pkg == self.package, result) m_subp.assert_has_calls([mock.call('list', opts=['--cacheonly'])]) + @mock.patch.object(util.ChrootableTarget, "__enter__", new=lambda a: a) + @mock.patch('curtin.distro.subp') + def test_has_pkg_available_suse_returns_false_not_avail(self, m_subp): + osfamily = distro.DISTROS.suse + m_subp.return_value = ('No matching items found.', '') + result = distro.has_pkg_available(self.package, self.target, osfamily) + self.assertEqual(False, result) + m_subp.assert_has_calls([mock.call(['zypper', '--quiet', 'search', + '--match-exact', self.package], + capture=True, + target=self.target)]) + class TestGetArchitecture(CiTestCase): @@ -563,4 +632,13 @@ class TestGetArchitecture(CiTestCase): self.m_rpm_get_arch.call_args_list) self.assertEqual(0, self.m_dpkg_get_arch.call_count) + def test_suse_osfamily_calls_rpm_get_arch(self): + osfamily = distro.DISTROS.suse + expected_result = self.m_rpm_get_arch.return_value + result = distro.get_architecture(target=self.target, osfamily=osfamily) + self.assertEqual(expected_result, result) + self.assertEqual([mock.call(target=self.target)], + self.m_rpm_get_arch.call_args_list) + self.assertEqual(0, self.m_dpkg_get_arch.call_count) + # vi: ts=4 expandtab syntax=python
-- Mailing list: https://launchpad.net/~curtin-dev Post to : curtin-dev@lists.launchpad.net Unsubscribe : https://launchpad.net/~curtin-dev More help : https://help.launchpad.net/ListHelp