diff --git a/cloudinit/distros/freebsd.py b/cloudinit/distros/freebsd.py index 91bf4a4..7245aa7 100644 --- a/cloudinit/distros/freebsd.py +++ b/cloudinit/distros/freebsd.py @@ -195,7 +195,7 @@ class Distro(distros.Distro): "gecos": '-c', "primary_group": '-g', "groups": '-G', - "passwd": '-h', + #"passwd": '-h', "shell": '-s', "inactive": '-E', } @@ -238,6 +238,17 @@ class Distro(distros.Distro): except Exception as e: util.logexc(LOG, "Failed to create user %s", name) raise e + ## We assume the password is always hashed + passwd_val = kwargs.get('passwd', None) + if passwd_val != None: + passwd_pipe = "echo '" + passwd_val + "' | pw usermod " + name + " -H 0" + passwd_log = "echo 'REDACTED' | pw usermod " + name + " -H 0" + try: + util.subp(['sh', '-c', passwd_pipe], logstring=passwd_log) + except Exception as e: + util.logexc(LOG, "Failed to set password for user %s", name) + raise e + def set_passwd(self, user, passwd, hashed=False): cmd = ['pw', 'usermod', user] diff --git a/cloudinit/net/__init__.py b/cloudinit/net/__init__.py index 465c2a0..648bd0b 100755 --- a/cloudinit/net/__init__.py +++ b/cloudinit/net/__init__.py @@ -26,7 +26,7 @@ from cloudinit import util LOG = logging.getLogger(__name__) SYS_CLASS_NET = "/sys/class/net/" DEFAULT_PRIMARY_INTERFACE = 'eth0' - +DEFAULT_PRIMARY_INTERFACE_FREEBSD = 'hn0' def sys_dev_path(devname, path=""): return SYS_CLASS_NET + devname + "/" + path @@ -47,6 +47,7 @@ def read_sys_net(devname, path, translate=None, if on_einval is not None: return on_einval(e) raise + contents = contents.strip() if translate is None: return contents @@ -85,8 +86,19 @@ def is_up(devname): # The linux kernel says to consider devices in 'unknown' # operstate as up for the purposes of network configuration. See # Documentation/networking/operstates.txt in the kernel source. - translate = {'up': True, 'unknown': True, 'down': False} - return read_sys_net_safe(devname, "operstate", translate=translate) + if util.is_FreeBSD(): + (if_result, _err) = util.subp(['ifconfig', devname], rcs = [0, 1]) + if len(_err): + return False + pat = "^" + devname + for item in if_result.split("\n"): + if re.match(pat, item): + flags = item.split('<')[1].split('>')[0] + if flags.find("UP") != -1: + return True + else: + translate = {'up': True, 'unknown': True, 'down': False} + return read_sys_net_safe(devname, "operstate", translate=translate) def is_wireless(devname): @@ -116,7 +128,11 @@ def is_present(devname): def get_devicelist(): - return os.listdir(SYS_CLASS_NET) + if util.is_FreeBSD(): + (nics, _err) = util.subp(['ifconfig', '-l'], rcs = [0, 1]) + return nics.split() + else: + return os.listdir(SYS_CLASS_NET) class ParserError(Exception): @@ -129,6 +145,50 @@ def is_disabled_cfg(cfg): return cfg.get('config') == "disabled" +def generate_fallback_config_freebsd(): + (nics, _err) = util.subp(['ifconfig', '-l', 'ether'], rcs = [0, 1]) + LOG.info("potential interfaces: %s", nics) + if len(_err): + LOG.info("Fail to get network interfaces") + return None + potential_interfaces = nics.split() + connected = [] + for nic in potential_interfaces: + pat = "^" + nic + LOG.info("NIC: %s\n", nic) + (if_result, _err) = util.subp(['ifconfig', nic], rcs = [0, 1]) + if len(_err): + continue + for item in if_result.split("\n"): + if re.match(pat, item): + flags = item.split('<')[1].split('>')[0] + if flags.find("RUNNING") != -1: + connected.append(nic) + if connected: + potential_interfaces = connected + names = list(sorted(potential_interfaces)) + default_pri_nic = DEFAULT_PRIMARY_INTERFACE_FREEBSD + if default_pri_nic in names: + names.remove(default_pri_nic) + names.insert(0, default_pri_nic) + target_name = None + target_mac = None + for name in names: + mac = get_interface_mac(name) + if mac: + target_name = name + target_mac = mac + break + if target_mac and target_name: + nconf = {'config': [], 'version': 1} + nconf['config'].append( + {'type': 'physical', 'name': target_name, + 'mac_address': target_mac, 'subnets': [{'type': 'dhcp'}]}) + return nconf + else: + return None + + def generate_fallback_config(): """Determine which attached net dev is most likely to have a connection and generate network state to run dhcp on that interface""" @@ -212,6 +272,28 @@ def apply_network_config_names(netcfg, strict_present=True, strict_busy=True): return _rename_interfaces(renames) +def _get_ipv6_global_perm_interface_FreeBSD(): + ipv6 = [] + nics, _err = util.subp(['ifconfig', '-l'], rcs = [0, 1]) + for nic in nics.split(): + if_result, _err = util.subp(['ifconfig', nic], rcs = [0, 1]) + for item in if_result.split("\n"): + if item.find("inet6 ") != -1 and item.find("scopeid") == -1: + ipv6.append(nic) + return ipv6 + +def _get_ipv4_FreeBSD(): + ipv4 = [] + ip_pat = re.compile(r"\s+inet\s+inet\s+\d+[.]\d+[.]\d+[.]\d+") + nics, _err = util.subp(['ifconfig', '-l'], rcs = [0, 1]) + for nic in nics.split(): + if_result, _err = util.subp(['ifconfig', nic], rcs = [0, 1]) + for item in if_result.split("\n"): + if ip_pat.match(item): + ipv4.append(nic) + return ipv4 + + def _get_current_rename_info(check_downable=True): """Collect information necessary for rename_interfaces.""" names = get_devicelist() @@ -222,13 +304,21 @@ def _get_current_rename_info(check_downable=True): if check_downable: nmatch = re.compile(r"[0-9]+:\s+(\w+)[@:]") - ipv6, _err = util.subp(['ip', '-6', 'addr', 'show', 'permanent', - 'scope', 'global'], capture=True) - ipv4, _err = util.subp(['ip', '-4', 'addr', 'show'], capture=True) - nics_with_addresses = set() - for bytes_out in (ipv6, ipv4): - nics_with_addresses.update(nmatch.findall(bytes_out)) + if util.is_FreeBSD(): + ipv6 = _get_ipv6_global_perm_interface_FreeBSD() + ipv4 = _get_ipv4_FreeBSD() + for bytes_out in (ipv6, ipv4): + for i in ipv6: + nics_with_addresses.update(i) + for i in ipv4: + nics_with_addresses.update(i) + else: + ipv6, _err = util.subp(['ip', '-6', 'addr', 'show', 'permanent', + 'scope', 'global'], capture=True) + ipv4, _err = util.subp(['ip', '-4', 'addr', 'show'], capture=True) + for bytes_out in (ipv6, ipv4): + nics_with_addresses.update(nmatch.findall(bytes_out)) for d in bymac.values(): d['downable'] = (d['up'] is False or @@ -349,12 +439,21 @@ def _rename_interfaces(renames, strict_present=True, strict_busy=True, def get_interface_mac(ifname): """Returns the string value of an interface's MAC Address""" - path = "address" - if os.path.isdir(sys_dev_path(ifname, "bonding_slave")): - # for a bond slave, get the nic's hwaddress, not the address it - # is using because its part of a bond. - path = "bonding_slave/perm_hwaddr" - return read_sys_net_safe(ifname, path) + if util.is_FreeBSD(): + (if_result, _err) = util.subp(['ifconfig', ifname], rcs = [0, 1]) + for item in if_result.split("\n"): + if item.find('ether ') != -1: + mac = str(item.split()[1]) + if mac: + return mac + + else: + path = "address" + if os.path.isdir(sys_dev_path(ifname, "bonding_slave")): + # for a bond slave, get the nic's hwaddress, not the address it + # is using because its part of a bond. + path = "bonding_slave/perm_hwaddr" + return read_sys_net_safe(ifname, path) def get_interfaces_by_mac(devs=None): diff --git a/cloudinit/settings.py b/cloudinit/settings.py index a968271..9f14752 100644 --- a/cloudinit/settings.py +++ b/cloudinit/settings.py @@ -49,7 +49,7 @@ CFG_BUILTIN = { ], 'def_log_file': '/var/log/cloud-init.log', 'log_cfgs': [], - 'syslog_fix_perms': ['syslog:adm', 'root:adm'], + 'syslog_fix_perms': ['syslog:adm', 'root:adm', 'root:wheel'], 'system_info': { 'paths': { 'cloud_dir': '/var/lib/cloud', diff --git a/cloudinit/sources/DataSourceAzure.py b/cloudinit/sources/DataSourceAzure.py index c46cd07..5e65d35 100644 --- a/cloudinit/sources/DataSourceAzure.py +++ b/cloudinit/sources/DataSourceAzure.py @@ -45,6 +45,20 @@ BOUNCE_COMMAND = [ # ensures that it gets linked to this path. RESOURCE_DISK_PATH = '/dev/disk/cloud/azure_resource' +FREEBSD_BUILTIN_DS_CONFIG = { + 'agent_command': AGENT_START_BUILTIN, + 'data_dir': "/var/lib/waagent", + 'set_hostname': True, + 'hostname_bounce': { + 'interface': 'hn0', + 'policy': True, + 'command': BOUNCE_COMMAND, + 'hostname_command': 'hostname', + }, + 'disk_aliases': {'ephemeral0': RESOURCE_DISK_PATH}, + 'dhclient_lease_file': '/var/db/dhclient.leases.hn0', +} + BUILTIN_DS_CONFIG = { 'agent_command': AGENT_START_BUILTIN, 'data_dir': "/var/lib/waagent", @@ -59,6 +73,17 @@ BUILTIN_DS_CONFIG = { 'dhclient_lease_file': '/var/lib/dhcp/dhclient.eth0.leases', } +FREEBSD_BUILTIN_CLOUD_CONFIG = { + 'disk_setup': { + 'ephemeral0': {'table_type': 'gpt', + 'layout': [100], + 'overwrite': True}, + }, + 'fs_setup': [{'filesystem': 'freebsd-ufs', + 'device': 'ephemeral0.1', + 'replace_fs': 'ntfs'}], +} + BUILTIN_CLOUD_CONFIG = { 'disk_setup': { 'ephemeral0': {'table_type': 'gpt', @@ -114,9 +139,14 @@ class DataSourceAzureNet(sources.DataSource): self.seed_dir = os.path.join(paths.seed_dir, 'azure') self.cfg = {} self.seed = None + buildin_ds_config = None + if util.is_FreeBSD(): + buildin_ds_config = FREEBSD_BUILTIN_DS_CONFIG + else: + buildin_ds_config = BUILTIN_DS_CONFIG self.ds_cfg = util.mergemanydict([ util.get_cfg_by_path(sys_cfg, DS_CFG_PATH, {}), - BUILTIN_DS_CONFIG]) + buildin_ds_config]) self.dhclient_lease_file = self.ds_cfg.get('dhclient_lease_file') def __str__(self): @@ -190,7 +220,10 @@ class DataSourceAzureNet(sources.DataSource): for cdev in candidates: try: if cdev.startswith("/dev/"): - ret = util.mount_cb(cdev, load_azure_ds_dir) + if util.is_FreeBSD(): + ret = util.mount_cb(cdev, load_azure_ds_dir, data=None, rw=False, mtype="udf", sync=False) + else: + ret = util.mount_cb(cdev, load_azure_ds_dir) else: ret = load_azure_ds_dir(cdev) @@ -205,7 +238,10 @@ class DataSourceAzureNet(sources.DataSource): (md, self.userdata_raw, cfg, files) = ret self.seed = cdev self.metadata = util.mergemanydict([md, DEFAULT_METADATA]) - self.cfg = util.mergemanydict([cfg, BUILTIN_CLOUD_CONFIG]) + if util.is_FreeBSD(): + self.cfg = util.mergemanydict([cfg, FREEBSD_BUILTIN_CLOUD_CONFIG]) + else: + self.cfg = util.mergemanydict([cfg, BUILTIN_CLOUD_CONFIG]) found = cdev LOG.debug("found datasource in %s", cdev) @@ -218,10 +254,12 @@ class DataSourceAzureNet(sources.DataSource): LOG.debug("using files cached in %s", ddir) # azure / hyper-v provides random data here - seed = util.load_file("/sys/firmware/acpi/tables/OEM0", - quiet=True, decode=False) - if seed: - self.metadata['random_seed'] = seed + if not util.is_FreeBSD(): + seed = util.load_file("/sys/firmware/acpi/tables/OEM0", + quiet=True, decode=False) + if seed: + self.metadata['random_seed'] = seed + ## TODO for FREEBSD # now update ds_cfg to reflect contents pass in config user_ds_cfg = util.get_cfg_by_path(self.cfg, DS_CFG_PATH, {}) @@ -321,6 +359,7 @@ def can_dev_be_reformatted(devpath): return True, bmsg + ' and had no important files. Safe for reformatting.' +## TODO. There is no RESOURECE_DISK_PATH on FREEBSD def address_ephemeral_resize(devpath=RESOURCE_DISK_PATH, maxwait=120, is_new_instance=False): # wait for ephemeral disk to come up @@ -631,8 +670,18 @@ def encrypt_pass(password, salt_id="$6$"): def list_possible_azure_ds_devs(): # return a sorted list of devices that might have a azure datasource devlist = [] - for fstype in ("iso9660", "udf"): - devlist.extend(util.find_devs_with("TYPE=%s" % fstype)) + if util.is_FreeBSD(): + cdrom_dev = "/dev/cd0" + (out, err) = util.subp(["mount", "-o", "ro", "-t", "udf", cdrom_dev, + "/mnt/cdrom/secure"], rcs=[0, 1]) + if len(err): + LOG.info("Fail to mount cd") + return devlist + util.subp(["umount", "/mnt/cdrom/secure"]) + devlist.append(cdrom_dev) + else: + for fstype in ("iso9660", "udf"): + devlist.extend(util.find_devs_with("TYPE=%s" % fstype)) devlist.sort(reverse=True) return devlist diff --git a/cloudinit/sources/helpers/azure.py b/cloudinit/sources/helpers/azure.py index 1b3e9b7..11fe82d 100644 --- a/cloudinit/sources/helpers/azure.py +++ b/cloudinit/sources/helpers/azure.py @@ -234,7 +234,11 @@ class WALinuxAgentShim(object): content = util.load_file(fallback_lease_file) LOG.debug("content is %s", content) for line in content.splitlines(): - if 'unknown-245' in line: + if util.is_FreeBSD(): + azure_endpoint = "option-245" + else: + azure_endpoint = "unknown-245" + if azure_endpoint in line: # Example line from Ubuntu # option unknown-245 a8:3f:81:10; leases.append(line.strip(' ').split(' ', 2)[-1].strip(';\n"')) diff --git a/cloudinit/stages.py b/cloudinit/stages.py index 86a1378..5c8f472 100644 --- a/cloudinit/stages.py +++ b/cloudinit/stages.py @@ -632,7 +632,10 @@ class Init(object): return (None, loc) if ncfg: return (ncfg, loc) - return (net.generate_fallback_config(), "fallback") + if util.is_FreeBSD(): + return (net.generate_fallback_config_freebsd(), "fallback") + else: + return (net.generate_fallback_config(), "fallback") def apply_network_config(self, bring_up): netcfg, src = self._find_networking_config() diff --git a/cloudinit/util.py b/cloudinit/util.py index cc08471..a2139dd 100644 --- a/cloudinit/util.py +++ b/cloudinit/util.py @@ -577,6 +577,10 @@ def is_ipv4(instr): return len(toks) == 4 +def is_FreeBSD(): + return system_info()['platform'].startswith('FreeBSD') + + def get_cfg_option_bool(yobj, key, default=False): if key not in yobj: return default @@ -2140,6 +2144,21 @@ def get_mount_info(path, log=LOG): # # So use /proc/$$/mountinfo to find the device underlying the # input path. + if is_FreeBSD(): + (result, err) = subp(['mount', '-p', path], rcs = [0, 1]) + ret=result.split() + label_part = ret[0] + if label_part.startswith("/dev/label/"): + target_label = label_part[5:] + (label_part, err) = subp(['glabel', 'status', '-s'], rcs = [0, 1]) + for labels in label_part.split("\n"): + items = labels.split() + if len(items) > 0 and items[0].startswith(target_label): + label_part = items[2] + break + label_part = str(label_part) + return "/dev/" + label_part, ret[2], ret[1] + mountinfo_path = '/proc/%s/mountinfo' % os.getpid() if os.path.exists(mountinfo_path): lines = load_file(mountinfo_path).splitlines() @@ -2364,7 +2383,8 @@ def read_dmi_data(key): uname_arch = os.uname()[4] if not (uname_arch == "x86_64" or (uname_arch.startswith("i") and uname_arch[2:] == "86") or - uname_arch == 'aarch64'): + uname_arch == 'aarch64' or + is_FreeBSD()): LOG.debug("dmidata is not supported on %s", uname_arch) return None