From 59a848c5929cbfca45d95860eb60dfebd0786c94 Mon Sep 17 00:00:00 2001 From: Gonéri Le Bouder Date: Mon, 14 Jun 2021 15:39:05 -0400 Subject: add DragonFlyBSD support (#904) - Mostly based on FreeBSD, the main exception is that `find_devs_with_on_freebsd` does not work. - Since we cannot get the CDROM or the partition labels, `find_devs_with_on_dragonflybsd()` has a more naive approach and returns all the block devices. --- README.md | 2 +- cloudinit/config/cc_growpart.py | 4 ++ cloudinit/config/cc_resizefs.py | 5 +++ cloudinit/distros/dragonflybsd.py | 12 ++++++ cloudinit/distros/freebsd.py | 15 +++++++- cloudinit/net/__init__.py | 6 +-- cloudinit/net/freebsd.py | 11 +++++- cloudinit/util.py | 46 ++++++++++++++++++++++- config/cloud.cfg.tmpl | 22 +++++++++-- doc/rtd/topics/availability.rst | 5 ++- setup.py | 2 +- sysvinit/freebsd/cloudinit | 2 +- tests/unittests/test_distros/test_dragonflybsd.py | 25 ++++++++++++ tests/unittests/test_util.py | 18 +++++++++ 14 files changed, 157 insertions(+), 18 deletions(-) create mode 100644 cloudinit/distros/dragonflybsd.py create mode 100644 tests/unittests/test_distros/test_dragonflybsd.py diff --git a/README.md b/README.md index c382b592..02b2f666 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ get in contact with that distribution and send them our way! | Supported OSes | Supported Public Clouds | Supported Private Clouds | | --- | --- | --- | -| Alpine Linux
ArchLinux
Debian
Fedora
FreeBSD
Gentoo Linux
NetBSD
OpenBSD
RHEL/CentOS/AlmaLinux/Rocky
SLES/openSUSE
Ubuntu










| Amazon Web Services
Microsoft Azure
Google Cloud Platform
Oracle Cloud Infrastructure
Softlayer
Rackspace Public Cloud
IBM Cloud
Digital Ocean
Bigstep
Hetzner
Joyent
CloudSigma
Alibaba Cloud
OVH
OpenNebula
Exoscale
Scaleway
CloudStack
AltCloud
SmartOS
HyperOne
Vultr
Rootbox
| Bare metal installs
OpenStack
LXD
KVM
Metal-as-a-Service (MAAS)















| +| Alpine Linux
ArchLinux
Debian
DragonFlyBSD
Fedora
FreeBSD
Gentoo Linux
NetBSD
OpenBSD
RHEL/CentOS/AlmaLinux/Rocky
SLES/openSUSE
Ubuntu










| Amazon Web Services
Microsoft Azure
Google Cloud Platform
Oracle Cloud Infrastructure
Softlayer
Rackspace Public Cloud
IBM Cloud
Digital Ocean
Bigstep
Hetzner
Joyent
CloudSigma
Alibaba Cloud
OVH
OpenNebula
Exoscale
Scaleway
CloudStack
AltCloud
SmartOS
HyperOne
Vultr
Rootbox
| Bare metal installs
OpenStack
LXD
KVM
Metal-as-a-Service (MAAS)















| ## To start developing cloud-init diff --git a/cloudinit/config/cc_growpart.py b/cloudinit/config/cc_growpart.py index 9f338ad1..9f5525a1 100644 --- a/cloudinit/config/cc_growpart.py +++ b/cloudinit/config/cc_growpart.py @@ -224,6 +224,10 @@ def device_part_info(devpath): freebsd_part = "/dev/" + util.find_freebsd_part(devpath) m = re.search('^(/dev/.+)p([0-9])$', freebsd_part) return (m.group(1), m.group(2)) + elif util.is_DragonFlyBSD(): + dragonflybsd_part = "/dev/" + util.find_dragonflybsd_part(devpath) + m = re.search('^(/dev/.+)s([0-9])$', dragonflybsd_part) + return (m.group(1), m.group(2)) if not os.path.exists(syspath): raise ValueError("%s had no syspath (%s)" % (devpath, syspath)) diff --git a/cloudinit/config/cc_resizefs.py b/cloudinit/config/cc_resizefs.py index 9afbb847..990a6939 100644 --- a/cloudinit/config/cc_resizefs.py +++ b/cloudinit/config/cc_resizefs.py @@ -85,6 +85,10 @@ def _resize_zfs(mount_point, devpth): return ('zpool', 'online', '-e', mount_point, devpth) +def _resize_hammer2(mount_point, devpth): + return ('hammer2', 'growfs', mount_point) + + def _can_skip_resize_ufs(mount_point, devpth): # possible errors cases on the code-path to growfs -N following: # https://github.com/freebsd/freebsd/blob/HEAD/sbin/growfs/growfs.c @@ -113,6 +117,7 @@ RESIZE_FS_PREFIXES_CMDS = [ ('xfs', _resize_xfs), ('ufs', _resize_ufs), ('zfs', _resize_zfs), + ('hammer2', _resize_hammer2), ] RESIZE_FS_PRECHECK_CMDS = { diff --git a/cloudinit/distros/dragonflybsd.py b/cloudinit/distros/dragonflybsd.py new file mode 100644 index 00000000..2d825518 --- /dev/null +++ b/cloudinit/distros/dragonflybsd.py @@ -0,0 +1,12 @@ +# Copyright (C) 2020-2021 Gonéri Le Bouder +# +# This file is part of cloud-init. See LICENSE file for license information. + +import cloudinit.distros.freebsd + + +class Distro(cloudinit.distros.freebsd.Distro): + home_dir = '/home' + + +# vi: ts=4 expandtab diff --git a/cloudinit/distros/freebsd.py b/cloudinit/distros/freebsd.py index 9659843f..d94a52b8 100644 --- a/cloudinit/distros/freebsd.py +++ b/cloudinit/distros/freebsd.py @@ -18,6 +18,12 @@ LOG = logging.getLogger(__name__) class Distro(cloudinit.distros.bsd.BSD): + """ + Distro subclass for FreeBSD. + + (N.B. DragonFlyBSD inherits from this class.) + """ + usr_lib_exec = '/usr/local/lib' login_conf_fn = '/etc/login.conf' login_conf_fn_bak = '/etc/login.conf.orig' @@ -28,6 +34,7 @@ class Distro(cloudinit.distros.bsd.BSD): pkg_cmd_update_prefix = ["pkg", "update"] pkg_cmd_upgrade_prefix = ["pkg", "upgrade"] prefer_fqdn = True # See rc.conf(5) in FreeBSD + home_dir = '/usr/home' def _get_add_member_to_group_cmd(self, member_name, group_name): return ['pw', 'usermod', '-n', member_name, '-G', group_name] @@ -66,9 +73,12 @@ class Distro(cloudinit.distros.bsd.BSD): pw_useradd_cmd.append('-d/nonexistent') log_pw_useradd_cmd.append('-d/nonexistent') else: - pw_useradd_cmd.append('-d/usr/home/%s' % name) + pw_useradd_cmd.append('-d{home_dir}/{name}'.format( + home_dir=self.home_dir, name=name)) pw_useradd_cmd.append('-m') - log_pw_useradd_cmd.append('-d/usr/home/%s' % name) + log_pw_useradd_cmd.append('-d{home_dir}/{name}'.format( + home_dir=self.home_dir, name=name)) + log_pw_useradd_cmd.append('-m') # Run the command @@ -155,4 +165,5 @@ class Distro(cloudinit.distros.bsd.BSD): "update-sources", self.package_command, ["update"], freq=PER_INSTANCE) + # vi: ts=4 expandtab diff --git a/cloudinit/net/__init__.py b/cloudinit/net/__init__.py index 6b3b84f7..b827d41a 100644 --- a/cloudinit/net/__init__.py +++ b/cloudinit/net/__init__.py @@ -351,7 +351,7 @@ def device_devid(devname): def get_devicelist(): - if util.is_FreeBSD(): + if util.is_FreeBSD() or util.is_DragonFlyBSD(): return list(get_interfaces_by_mac().values()) try: @@ -376,7 +376,7 @@ def is_disabled_cfg(cfg): def find_fallback_nic(blacklist_drivers=None): """Return the name of the 'fallback' network device.""" - if util.is_FreeBSD(): + if util.is_FreeBSD() or util.is_DragonFlyBSD(): return find_fallback_nic_on_freebsd(blacklist_drivers) elif util.is_NetBSD() or util.is_OpenBSD(): return find_fallback_nic_on_netbsd_or_openbsd(blacklist_drivers) @@ -816,7 +816,7 @@ def get_ib_interface_hwaddr(ifname, ethernet_format): def get_interfaces_by_mac(blacklist_drivers=None) -> dict: - if util.is_FreeBSD(): + if util.is_FreeBSD() or util.is_DragonFlyBSD(): return get_interfaces_by_mac_on_freebsd( blacklist_drivers=blacklist_drivers) elif util.is_NetBSD(): diff --git a/cloudinit/net/freebsd.py b/cloudinit/net/freebsd.py index c843d792..f8faf240 100644 --- a/cloudinit/net/freebsd.py +++ b/cloudinit/net/freebsd.py @@ -32,6 +32,13 @@ class Renderer(cloudinit.net.bsd.BSDRenderer): LOG.debug("freebsd generate postcmd disabled") return + for dhcp_interface in self.dhcp_interfaces(): + # Observed on DragonFlyBSD 6. If we use the "restart" parameter, + # the routes are not recreated. + subp.subp(['service', 'dhclient', 'stop', dhcp_interface], + rcs=[0, 1], + capture=True) + subp.subp(['service', 'netif', 'restart'], capture=True) # On FreeBSD 10, the restart of routing and dhclient is likely to fail # because @@ -42,7 +49,7 @@ class Renderer(cloudinit.net.bsd.BSDRenderer): subp.subp(['service', 'routing', 'restart'], capture=True, rcs=[0, 1]) for dhcp_interface in self.dhcp_interfaces(): - subp.subp(['service', 'dhclient', 'restart', dhcp_interface], + subp.subp(['service', 'dhclient', 'start', dhcp_interface], rcs=[0, 1], capture=True) @@ -57,4 +64,4 @@ class Renderer(cloudinit.net.bsd.BSDRenderer): def available(target=None): - return util.is_FreeBSD() + return util.is_FreeBSD() or util.is_DragonFlyBSD() diff --git a/cloudinit/util.py b/cloudinit/util.py index 2de1123e..f95dc435 100644 --- a/cloudinit/util.py +++ b/cloudinit/util.py @@ -392,7 +392,11 @@ def is_Linux(): @lru_cache() def is_BSD(): - return 'BSD' in platform.system() + if 'BSD' in platform.system(): + return True + if platform.system() == 'DragonFly': + return True + return False @lru_cache() @@ -400,6 +404,11 @@ def is_FreeBSD(): return system_info()['variant'] == "freebsd" +@lru_cache() +def is_DragonFlyBSD(): + return system_info()['variant'] == "dragonfly" + + @lru_cache() def is_NetBSD(): return system_info()['variant'] == "netbsd" @@ -534,7 +543,9 @@ def system_info(): var = 'suse' else: var = 'linux' - elif system in ('windows', 'darwin', "freebsd", "netbsd", "openbsd"): + elif system in ( + 'windows', 'darwin', "freebsd", "netbsd", + "openbsd", "dragonfly"): var = system info['variant'] = var @@ -1195,6 +1206,23 @@ def find_devs_with_openbsd(criteria=None, oformat='device', return ['/dev/' + i for i in devlist] +def find_devs_with_dragonflybsd(criteria=None, oformat='device', + tag=None, no_cache=False, path=None): + out, _err = subp.subp(['sysctl', '-n', 'kern.disks'], rcs=[0]) + devlist = [i for i in sorted(out.split(), reverse=True) + if not i.startswith("md") and not i.startswith("vn")] + + if criteria == "TYPE=iso9660": + devlist = [i for i in devlist + if i.startswith('cd') or i.startswith('acd')] + elif criteria in ["LABEL=CONFIG-2", "TYPE=vfat"]: + devlist = [i for i in devlist + if not (i.startswith('cd') or i.startswith('acd'))] + elif criteria: + LOG.debug("Unexpected criteria: %s", criteria) + return ['/dev/' + i for i in devlist] + + def find_devs_with(criteria=None, oformat='device', tag=None, no_cache=False, path=None): """ @@ -1213,6 +1241,9 @@ def find_devs_with(criteria=None, oformat='device', elif is_OpenBSD(): return find_devs_with_openbsd(criteria, oformat, tag, no_cache, path) + elif is_DragonFlyBSD(): + return find_devs_with_dragonflybsd(criteria, oformat, + tag, no_cache, path) blk_id_cmd = ['blkid'] options = [] @@ -2211,6 +2242,14 @@ def find_freebsd_part(fs): LOG.warning("Unexpected input in find_freebsd_part: %s", fs) +def find_dragonflybsd_part(fs): + splitted = fs.split('/') + if len(splitted) == 3 and splitted[1] == 'dev': + return splitted[2] + else: + LOG.warning("Unexpected input in find_dragonflybsd_part: %s", fs) + + def get_path_dev_freebsd(path, mnt_list): path_found = None for line in mnt_list.split("\n"): @@ -2264,6 +2303,9 @@ def parse_mount(path): # https://regex101.com/r/T2en7a/1 regex = (r'^(/dev/[\S]+|.*zroot\S*?) on (/[\S]*) ' r'(?=(?:type)[\s]+([\S]+)|\(([^,]*))') + if is_DragonFlyBSD(): + regex = (r'^(/dev/[\S]+|\S*?) on (/[\S]*) ' + r'(?=(?:type)[\s]+([\S]+)|\(([^,]*))') for line in mount_locs: m = re.search(regex, line) if not m: diff --git a/config/cloud.cfg.tmpl b/config/cloud.cfg.tmpl index 2f6c3a7d..586384e4 100644 --- a/config/cloud.cfg.tmpl +++ b/config/cloud.cfg.tmpl @@ -1,8 +1,8 @@ ## template:jinja # The top level settings are used as module # and system configuration. - -{% if variant.endswith("bsd") %} +{% set is_bsd = variant in ["dragonfly", "freebsd", "netbsd", "openbsd"] %} +{% if is_bsd %} syslog_fix_perms: root:wheel {% elif variant in ["suse"] %} syslog_fix_perms: root:root @@ -61,11 +61,11 @@ cloud_init_modules: {% endif %} - bootcmd - write-files -{% if variant not in ["netbsd"] %} +{% if variant not in ["netbsd", "openbsd"] %} - growpart - resizefs {% endif %} -{% if variant not in ["freebsd", "netbsd"] %} +{% if not is_bsd %} - disk_setup - mounts {% endif %} @@ -158,6 +158,8 @@ system_info: "fedora", "freebsd", "netbsd", "openbsd", "rhel", "rocky", "suse", "ubuntu"] %} distro: {{ variant }} +{% elif variant in ["dragonfly"] %} + distro: dragonflybsd {% else %} # Unknown/fallback distro. distro: ubuntu @@ -249,6 +251,15 @@ system_info: groups: [wheel] sudo: ["ALL=(ALL) NOPASSWD:ALL"] shell: /bin/tcsh +{% elif variant in ["dragonfly"] %} + # Default user name + that default users groups (if added/used) + default_user: + name: dragonfly + lock_passwd: True + gecos: DragonFly + groups: [wheel] + sudo: ["ALL=(ALL) NOPASSWD:ALL"] + shell: /bin/sh {% elif variant in ["netbsd"] %} default_user: name: netbsd @@ -269,4 +280,7 @@ system_info: {% if variant in ["freebsd", "netbsd", "openbsd"] %} network: renderers: ['{{ variant }}'] +{% elif variant in ["dragonfly"] %} + network: + renderers: ['freebsd'] {% endif %} diff --git a/doc/rtd/topics/availability.rst b/doc/rtd/topics/availability.rst index f3e13edc..e4480754 100644 --- a/doc/rtd/topics/availability.rst +++ b/doc/rtd/topics/availability.rst @@ -14,12 +14,13 @@ distributions and clouds, both public and private. Distributions ============= -Cloud-init has support across all major Linux distributions, FreeBSD, NetBSD -and OpenBSD: +Cloud-init has support across all major Linux distributions, FreeBSD, NetBSD, +OpenBSD and DragonFlyBSD: - Alpine Linux - ArchLinux - Debian +- DragonFlyBSD - Fedora - FreeBSD - Gentoo Linux diff --git a/setup.py b/setup.py index cbacf48e..dcbe0843 100755 --- a/setup.py +++ b/setup.py @@ -156,7 +156,7 @@ USR = "usr" ETC = "etc" USR_LIB_EXEC = "usr/lib" LIB = "lib" -if os.uname()[0] == 'FreeBSD': +if os.uname()[0] in ['FreeBSD', 'DragonFly']: USR = "usr/local" USR_LIB_EXEC = "usr/local/lib" elif os.path.isfile('/etc/redhat-release'): diff --git a/sysvinit/freebsd/cloudinit b/sysvinit/freebsd/cloudinit index aa5bd118..d26f3d0f 100755 --- a/sysvinit/freebsd/cloudinit +++ b/sysvinit/freebsd/cloudinit @@ -2,7 +2,7 @@ # PROVIDE: cloudinit # REQUIRE: FILESYSTEMS NETWORKING cloudinitlocal ldconfig devd -# BEFORE: cloudconfig cloudfinal +# BEFORE: LOGIN cloudconfig cloudfinal . /etc/rc.subr diff --git a/tests/unittests/test_distros/test_dragonflybsd.py b/tests/unittests/test_distros/test_dragonflybsd.py new file mode 100644 index 00000000..df2c00f4 --- /dev/null +++ b/tests/unittests/test_distros/test_dragonflybsd.py @@ -0,0 +1,25 @@ +#!/usr/bin/env python3 + + +import cloudinit.util +from cloudinit.tests.helpers import mock + + +def test_find_dragonflybsd_part(): + assert cloudinit.util.find_dragonflybsd_part("/dev/vbd0s3") == "vbd0s3" + + +@mock.patch("cloudinit.util.is_DragonFlyBSD") +@mock.patch("cloudinit.subp.subp") +def test_parse_mount(mock_subp, m_is_DragonFlyBSD): + mount_out = """ +vbd0s3 on / (hammer2, local) +devfs on /dev (devfs, nosymfollow, local) +/dev/vbd0s0a on /boot (ufs, local) +procfs on /proc (procfs, local) +tmpfs on /var/run/shm (tmpfs, local) +""" + + mock_subp.return_value = (mount_out, "") + m_is_DragonFlyBSD.return_value = True + assert cloudinit.util.parse_mount("/") == ("vbd0s3", "hammer2", "/") diff --git a/tests/unittests/test_util.py b/tests/unittests/test_util.py index e5292001..2290cab7 100644 --- a/tests/unittests/test_util.py +++ b/tests/unittests/test_util.py @@ -999,4 +999,22 @@ class TestFindDevs: devlist = util.find_devs_with_netbsd(criteria=criteria) assert devlist == expected_devlist + @pytest.mark.parametrize( + 'criteria,expected_devlist', ( + (None, ['/dev/vbd0', '/dev/cd0', '/dev/acd0']), + ('TYPE=iso9660', ['/dev/cd0', '/dev/acd0']), + ('TYPE=vfat', ['/dev/vbd0']), + ('LABEL_FATBOOT=A_LABEL', # lp: #1841466 + ['/dev/vbd0', '/dev/cd0', '/dev/acd0']), + ) + ) + @mock.patch("cloudinit.subp.subp") + def test_find_devs_with_dragonflybsd(self, m_subp, criteria, + expected_devlist): + m_subp.return_value = ( + 'md2 md1 cd0 vbd0 acd0 vn3 vn2 vn1 vn0 md0', '' + ) + devlist = util.find_devs_with_dragonflybsd(criteria=criteria) + assert devlist == expected_devlist + # vi: ts=4 expandtab -- cgit v1.2.3