← Back to team overview

cloud-init-dev team mailing list archive

[Merge] ~redriver/cloud-init:frbsd-azure-branch into cloud-init:master

 

Hongjiang Zhang has proposed merging ~redriver/cloud-init:frbsd-azure-branch into cloud-init:master.

Requested reviews:
  cloud init development team (cloud-init-dev)

For more details, see:
https://code.launchpad.net/~redriver/cloud-init/+git/cloud-init/+merge/314794
-- 
Your team cloud init development team is requested to review the proposed merge of ~redriver/cloud-init:frbsd-azure-branch into cloud-init:master.
diff --git a/cloudinit/config/cc_growpart.py b/cloudinit/config/cc_growpart.py
index 832bb3f..6c989c9 100644
--- a/cloudinit/config/cc_growpart.py
+++ b/cloudinit/config/cc_growpart.py
@@ -240,22 +240,22 @@ def device_part_info(devpath):
     return (diskdevpath, ptnum)
 
 
-def devent2dev(devent):
+def devent2dev(devent, cloud):
     if devent.startswith("/dev/"):
         return devent
     else:
-        result = util.get_mount_info(devent)
+        result = cloud.datasource.get_mount_info(devent)
         if not result:
             raise ValueError("Could not determine device of '%s' % dev_ent")
         return result[0]
 
 
-def resize_devices(resizer, devices):
+def resize_devices(resizer, devices, cloud):
     # returns a tuple of tuples containing (entry-in-devices, action, message)
     info = []
     for devent in devices:
         try:
-            blockdev = devent2dev(devent)
+            blockdev = devent2dev(devent, cloud)
         except ValueError as e:
             info.append((devent, RESIZE.SKIPPED,
                          "unable to convert to device: %s" % e,))
@@ -335,7 +335,7 @@ def handle(_name, cfg, _cloud, log, _args):
         return
 
     resized = util.log_time(logfunc=log.debug, msg="resize_devices",
-                            func=resize_devices, args=(resizer, devices))
+                            func=resize_devices, args=(resizer, devices, _cloud))
     for (entry, action, msg) in resized:
         if action == RESIZE.CHANGED:
             log.info("'%s' resized: %s" % (entry, msg))
diff --git a/cloudinit/config/cc_resizefs.py b/cloudinit/config/cc_resizefs.py
index e028abf..a173006 100644
--- a/cloudinit/config/cc_resizefs.py
+++ b/cloudinit/config/cc_resizefs.py
@@ -106,7 +106,7 @@ def handle(name, cfg, _cloud, log, args):
 
     # TODO(harlowja): allow what is to be resized to be configurable??
     resize_what = "/"
-    result = util.get_mount_info(resize_what, log)
+    result = _cloud.datasource.get_mount_info(resize_what, log)
     if not result:
         log.warn("Could not determine filesystem type of %s", resize_what)
         return
diff --git a/cloudinit/distros/__init__.py b/cloudinit/distros/__init__.py
index f3d395b..d1b6844 100755
--- a/cloudinit/distros/__init__.py
+++ b/cloudinit/distros/__init__.py
@@ -142,6 +142,9 @@ class Distro(object):
             ns, header=header, render_hwaddress=True)
         return self.apply_network(contents, bring_up=bring_up)
 
+    def generate_fallback_config(self):
+        return net.generate_fallback_config()
+
     def apply_network_config(self, netconfig, bring_up=False):
         # apply network config netconfig
         # This method is preferred to apply_network which only takes
diff --git a/cloudinit/distros/freebsd.py b/cloudinit/distros/freebsd.py
index a70ee45..e69a254 100644
--- a/cloudinit/distros/freebsd.py
+++ b/cloudinit/distros/freebsd.py
@@ -30,6 +30,7 @@ class Distro(distros.Distro):
     login_conf_fn_bak = '/etc/login.conf.orig'
     resolv_conf_fn = '/etc/resolv.conf'
     ci_sudoers_fn = '/usr/local/etc/sudoers.d/90-cloud-init-users'
+    default_primary_nic = 'hn0'
 
     def __init__(self, name, cfg, paths):
         distros.Distro.__init__(self, name, cfg, paths)
@@ -183,7 +184,6 @@ class Distro(distros.Distro):
             "gecos": '-c',
             "primary_group": '-g',
             "groups": '-G',
-            "passwd": '-h',
             "shell": '-s',
             "inactive": '-E',
         }
@@ -193,19 +193,11 @@ class Distro(distros.Distro):
             "no_log_init": '--no-log-init',
         }
 
-        redact_opts = ['passwd']
-
         for key, val in kwargs.items():
             if (key in adduser_opts and val and
                isinstance(val, six.string_types)):
                 adduser_cmd.extend([adduser_opts[key], val])
 
-                # Redact certain fields from the logs
-                if key in redact_opts:
-                    log_adduser_cmd.extend([adduser_opts[key], 'REDACTED'])
-                else:
-                    log_adduser_cmd.extend([adduser_opts[key], val])
-
             elif key in adduser_flags and val:
                 adduser_cmd.append(adduser_flags[key])
                 log_adduser_cmd.append(adduser_flags[key])
@@ -226,19 +218,29 @@ class Distro(distros.Distro):
         except Exception as e:
             util.logexc(LOG, "Failed to create user %s", name)
             raise e
+        # Set the password if it is provided
+        # For security consideration, only hashed passwd is assumed
+        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]
+        passwd_pipe = "echo '" + passwd + "' | pw usermod " + user
 
         if hashed:
-            cmd.append('-H')
+            passwd_pipe = passwd_pipe + " -H 0"
         else:
-            cmd.append('-h')
-
-        cmd.append('0')
+            passwd_pipe = passwd_pipe + " -h 0"
 
         try:
-            util.subp(cmd, passwd, logstring="chpasswd for %s" % user)
+            util.subp(['sh', '-c', passwd_pipe],
+                      logstring="chpasswd for %s" % user)
         except Exception as e:
             util.logexc(LOG, "Failed to set password for %s", user)
             raise e
@@ -271,6 +273,230 @@ class Distro(distros.Distro):
             keys = set(kwargs['ssh_authorized_keys']) or []
             ssh_util.setup_user_keys(keys, name, options=None)
 
+    def _get_interface_mac(self, ifname):
+        (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
+
+    def _get_devicelist(self):
+        (nics, _err) = util.subp(['ifconfig', '-l'], rcs = [0, 1])
+        return nics.split()
+
+    def _get_ipv6(self):
+        ipv6 = []
+        nics = self._get_devicelist()
+        for nic in nics:
+            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(self):
+        ipv4 = []
+        ip_pat = re.compile(r"\s+inet\s+inet\s+\d+[.]\d+[.]\d+[.]\d+")
+        nics = self._get_devicelist()
+        for nic in nics:
+            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 _is_up(self, ifname):
+        (if_result, _err) = util.subp(['ifconfig', ifname], rcs = [0, 1])
+        if len(_err):
+            return False
+        pat = "^" + ifname
+        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
+
+    def _get_current_rename_info(self, check_downable=True):
+        """Collect information necessary for rename_interfaces."""
+        names = self._get_devicelist()
+        bymac = {}
+        for n in names:
+            bymac[self._get_interface_mac(n)] = {
+                'name': n, 'up': self._is_up(n), 'downable': None}
+
+        if check_downable:
+            nmatch = re.compile(r"[0-9]+:\s+(\w+)[@:]")
+            nics_with_addresses = set()
+            ipv6 = self._get_ipv6()
+            ipv4 = self._get_ipv4()
+            for bytes_out in (ipv6, ipv4):
+                for i in ipv6:
+                    nics_with_addresses.update(i)
+                for i in ipv4:
+                    nics_with_addresses.update(i)
+
+        for d in bymac.values():
+            d['downable'] = (d['up'] is False or
+                             d['name'] not in nics_with_addresses)
+
+        return bymac
+
+    def _rename_interfaces(self, renames):
+        if not len(renames):
+            LOG.debug("no interfaces to rename")
+            return
+
+        current_info = self._get_current_rename_info()
+
+        cur_bymac = {}
+        for mac, data in current_info.items():
+            cur = data.copy()
+            cur['mac'] = mac
+            cur_bymac[mac] = cur
+
+        def update_byname(bymac):
+            return dict((data['name'], data)
+                        for data in bymac.values())
+
+        def rename(cur, new):
+            util.subp(["ifconfig", cur, "name", new], capture=True)
+
+        def down(name):
+            util.subp(["ifconfig", name, "down"], capture=True)
+
+        def up(name):
+            util.subp(["ifconfig", name, "up"], capture=True)
+
+        ops = []
+        errors = []
+        ups = []
+        cur_byname = update_byname(cur_bymac)
+        tmpname_fmt = "cirename%d"
+        tmpi = -1
+
+        for mac, new_name in renames:
+            cur = cur_bymac.get(mac, {})
+            cur_name = cur.get('name')
+            cur_ops = []
+            if cur_name == new_name:
+                # nothing to do
+                continue
+
+            if not cur_name:
+                errors.append("[nic not present] Cannot rename mac=%s to %s"
+                    ", not available." % (mac, new_name))
+                continue
+
+            if cur['up']:
+                msg = "[busy] Error renaming mac=%s from %s to %s"
+                if not cur['downable']:
+                    errors.append(msg % (mac, cur_name, new_name))
+                    continue
+                cur['up'] = False
+                cur_ops.append(("down", mac, new_name, (cur_name,)))
+                ups.append(("up", mac, new_name, (new_name,)))
+
+            if new_name in cur_byname:
+                target = cur_byname[new_name]
+                if target['up']:
+                    msg = "[busy-target] Error renaming mac=%s from %s to %s."
+                    if not target['downable']:
+                        errors.append(msg % (mac, cur_name, new_name))
+                        continue
+                    else:
+                        cur_ops.append(("down", mac, new_name, (new_name,)))
+
+                tmp_name = None
+                while tmp_name is None or tmp_name in cur_byname:
+                    tmpi += 1
+                    tmp_name = tmpname_fmt % tmpi
+
+                cur_ops.append(("rename", mac, new_name, (new_name, tmp_name)))
+                target['name'] = tmp_name
+                cur_byname = update_byname(cur_bymac)
+                if target['up']:
+                    ups.append(("up", mac, new_name, (tmp_name,)))
+
+            cur_ops.append(("rename", mac, new_name, (cur['name'], new_name)))
+            cur['name'] = new_name
+            cur_byname = update_byname(cur_bymac)
+            ops += cur_ops
+
+        opmap = {'rename': rename, 'down': down, 'up': up}
+        if len(ops) + len(ups) == 0:
+            if len(errors):
+                LOG.debug("unable to do any work for renaming of %s", renames)
+            else:
+                LOG.debug("no work necessary for renaming of %s", renames)
+        else:
+            LOG.debug("achieving renaming of %s with ops %s", renames, ops + ups)
+
+            for op, mac, new_name, params in ops + ups:
+                try:
+                    opmap.get(op)(*params)
+                except Exception as e:
+                    errors.append(
+                        "[unknown] Error performing %s%s for %s, %s: %s" %
+                        (op, params, mac, new_name, e))
+        if len(errors):
+            raise Exception('\n'.join(errors))
+
+    def apply_network_config_names(self, netcfg):
+        renames = []
+        for ent in netcfg.get('config', {}):
+            if ent.get('type') != 'physical':
+                continue
+            mac = ent.get('mac_address')
+            name = ent.get('name')
+            if not mac:
+                continue
+            renames.append([mac, name])
+        return self._rename_interfaces(renames)
+
+    def generate_fallback_config(self):
+        (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 = self.default_primary_nic
+        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 = self._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 _write_network(self, settings):
         entries = net_util.translate_network(settings)
         nameservers = []
diff --git a/cloudinit/settings.py b/cloudinit/settings.py
index b1fdd31..1cf0290 100644
--- a/cloudinit/settings.py
+++ b/cloudinit/settings.py
@@ -37,7 +37,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 c5af8b8..aa7b4a7 100644
--- a/cloudinit/sources/DataSourceAzure.py
+++ b/cloudinit/sources/DataSourceAzure.py
@@ -32,19 +32,87 @@ BOUNCE_COMMAND = [
 # azure systems will always have a resource disk, and 66-azure-ephemeral.rules
 # ensures that it gets linked to this path.
 RESOURCE_DISK_PATH = '/dev/disk/cloud/azure_resource'
+DEFAULT_PRIMARY_NIC = 'eth0'
+LEASE_FILE = '/var/lib/dhcp/dhclient.eth0.leases'
+DEFAULT_FS = 'ext4'
+
+def get_resource_disk_on_freebsd(port_id):
+    g0 = "00000000"
+    if port_id > 1:
+        g0 = "00000001"
+        port_id = port_id - 2
+    """
+    search 'X' from 
+       'dev.storvsc.X.%pnpinfo: 
+           classid=32412632-86cb-44a2-9b5c-50d1417354f5 
+           deviceid=00000000-0001-8899-0000-000000000000'
+    """
+    pipe_cmd = 'sysctl dev.storvsc | grep pnpinfo | grep deviceid='
+    out, err = util.subp(['sh', '-c', pipe_cmd], rcs = [0, 1])
+    if len(err) != 0:
+        return None
+    g1 = "000" + str(port_id)
+    g0g1 = "{0}-{1}".format(g0, g1)
+    """
+    search 'X' from
+       'dev.storvsc.X.%pnpinfo: 
+            classid=32412632-86cb-44a2-9b5c-50d1417354f5 
+            deviceid=00000000-0001-8899-0000-000000000000'
+    """
+    cmd_search_ide = "sysctl dev.storvsc | grep pnpinfo | grep deviceid={0}".format(g0g1)
+    out, err = util.subp(['sh', '-c', cmd_search_ide], rcs = [0, 1])
+    if len(err) != 0:
+        return None
+    cmd_extract_id = cmd_search_ide + "|awk -F . '{print $3}'"
+    out, err = util.subp(['sh', '-c', cmd_extract_id], rcs = [0, 1])
+    """
+    try to search 'blkvscX' and 'storvscX' to find device name
+    """
+    out = out.rstrip()
+    cmd_search_blkvsc = "camcontrol devlist -b | grep blkvsc{0} | awk '{{print $1}}'".format(out)
+    out, err = util.subp(['sh', '-c', cmd_search_blkvsc], rcs = [0, 1])
+    if len(err) == 0:
+        out = out.rstrip()
+        cmd_search_dev = "camcontrol devlist | grep {0} | sed 's/(/_/g' | awk -F _ '{{print $2}}' | awk -F , '{{print $1}}'".format(out)
+        out, err = util.subp(['sh', '-c', cmd_search_dev], rcs = [0, 1])
+        if len(err) == 0:
+            return out.rstrip()
+        else:
+            return None
+    cmd_search_storvsc = "camcontrol devlist -b | grep storvsc{0} | awk '{{print $1}}'".format(out)
+    out, err = util.subp(['sh', '-c', cmd_search_storvsc], rcs = [0, 1])
+    if len(err) == 0:
+        out = out.rstrip()
+        cmd_search_dev = "camcontrol devlist | grep {0} | sed 's/(/_/g' | awk -F _ '{{print $2}}'|awk -F , '{{print $1}}'".format(out)
+        out, err = util.subp(['sh', '-c', cmd_search_dev], rcs = [0, 1])
+        if len(err) == 0:
+            return out.rstrip()
+    return None
+
+## update the FreeBSD specific information
+if util.is_FreeBSD():
+    DEFAULT_PRIMARY_NIC = 'hn0'
+    LEASE_FILE = '/var/db/dhclient.leases.hn0'
+    DEFAULT_FS = 'freebsd-ufs'
+    res_disk = get_resource_disk_on_freebsd(1)
+    if res_disk != None:
+        LOG.debug("resource disk is not None")
+        RESOURCE_DISK_PATH = "/dev/" + res_disk
+    else:
+        LOG.debug("resource disk is None")
 
 BUILTIN_DS_CONFIG = {
     'agent_command': AGENT_START_BUILTIN,
     'data_dir': "/var/lib/waagent",
     'set_hostname': True,
     'hostname_bounce': {
-        'interface': 'eth0',
+        'interface': DEFAULT_PRIMARY_NIC,
         'policy': True,
         'command': BOUNCE_COMMAND,
         'hostname_command': 'hostname',
     },
     'disk_aliases': {'ephemeral0': RESOURCE_DISK_PATH},
-    'dhclient_lease_file': '/var/lib/dhcp/dhclient.eth0.leases',
+    'dhclient_lease_file': LEASE_FILE,
 }
 
 BUILTIN_CLOUD_CONFIG = {
@@ -53,7 +121,7 @@ BUILTIN_CLOUD_CONFIG = {
                        'layout': [100],
                        'overwrite': True},
     },
-    'fs_setup': [{'filesystem': 'ext4',
+    'fs_setup': [{'filesystem': DEFAULT_FS,
                   'device': 'ephemeral0.1',
                   'replace_fs': 'ntfs'}],
 }
@@ -178,7 +246,11 @@ 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,
+                                            mtype="udf", sync=False)
+                    else:
+                        ret = util.mount_cb(cdev, load_azure_ds_dir)
                 else:
                     ret = load_azure_ds_dir(cdev)
 
@@ -206,11 +278,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",
+        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
-
+            if seed:
+                self.metadata['random_seed'] = seed
+        # TODO. find the seed on FreeBSD platform
         # now update ds_cfg to reflect contents pass in config
         user_ds_cfg = util.get_cfg_by_path(self.cfg, DS_CFG_PATH, {})
         self.ds_cfg = util.mergemanydict([user_ds_cfg, self.ds_cfg])
@@ -251,6 +324,23 @@ class DataSourceAzureNet(sources.DataSource):
         address_ephemeral_resize(is_new_instance=is_new_instance)
         return
 
+    def get_mount_info(self, path, log=LOG):
+        if not util.is_FreeBSD():
+            return sources.DataSource.get_mount_info(path, log)
+        ## mount information for FreeBSD on Azure
+        (result, err) = util.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) = util.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]
 
 def can_dev_be_reformatted(devpath):
     # determine if the ephemeral block device path devpath
@@ -619,8 +709,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 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/__init__.py b/cloudinit/sources/__init__.py
index 3d01072..38550a7 100644
--- a/cloudinit/sources/__init__.py
+++ b/cloudinit/sources/__init__.py
@@ -222,6 +222,9 @@ class DataSource(object):
         # quickly (local check only) if self.instance_id is still
         return False
 
+    def get_mount_info(self, path, log=LOG):
+        return util.get_mount_info(path, log)
+
     @staticmethod
     def _determine_dsmode(candidates, default=None, valid=None):
         # return the first candidate that is non None, warn if not valid
diff --git a/cloudinit/sources/helpers/azure.py b/cloudinit/sources/helpers/azure.py
index f32dac9..f2e2efd 100644
--- a/cloudinit/sources/helpers/azure.py
+++ b/cloudinit/sources/helpers/azure.py
@@ -236,7 +236,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 b0552dd..be26f31 100644
--- a/cloudinit/stages.py
+++ b/cloudinit/stages.py
@@ -616,7 +616,7 @@ class Init(object):
                 return (None, loc)
             if ncfg:
                 return (ncfg, loc)
-        return (net.generate_fallback_config(), "fallback")
+        return (self.distro.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 7196a7c..de0052b 100644
--- a/cloudinit/util.py
+++ b/cloudinit/util.py
@@ -564,6 +564,8 @@ 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:
@@ -2313,7 +2315,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
+            uname_arch == 'amd64'):
         LOG.debug("dmidata is not supported on %s", uname_arch)
         return None
 
diff --git a/config/cloud.cfg-freebsd-azure b/config/cloud.cfg-freebsd-azure
new file mode 100644
index 0000000..27a028a
--- /dev/null
+++ b/config/cloud.cfg-freebsd-azure
@@ -0,0 +1,86 @@
+# The top level settings are used as module
+# and system configuration.
+
+syslog_fix_perms: root:wheel
+
+# This should not be required, but leave it in place until the real cause of
+# not beeing able to find -any- datasources is resolved.
+datasource_list: ['ConfigDrive', 'Azure']
+
+# A set of users which may be applied and/or used by various modules
+# when a 'default' entry is found it will reference the 'default_user'
+# from the distro configuration specified below
+users:
+   - default
+
+# If this is set, 'root' will not be able to ssh in and they 
+# will get a message to login instead as the above $user (ubuntu)
+disable_root: false
+
+# This will cause the set+update hostname module to not operate (if true)
+preserve_hostname: false
+
+# Example datasource config
+# datasource: 
+#    Ec2: 
+#      metadata_urls: [ 'blah.com' ]
+#      timeout: 5 # (defaults to 50 seconds)
+#      max_wait: 10 # (defaults to 120 seconds)
+
+# The modules that run in the 'init' stage
+cloud_init_modules:
+# - migrator
+ - seed_random
+ - bootcmd
+# - write-files
+ - growpart
+# - resizefs
+ - set_hostname
+ - update_hostname
+# - update_etc_hosts
+# - ca-certs
+# - rsyslog
+ - users-groups
+ - ssh
+
+# The modules that run in the 'config' stage
+cloud_config_modules:
+# - disk_setup
+# - mounts
+ - ssh-import-id
+ - locale
+ - set-passwords
+ - package-update-upgrade-install
+# - landscape
+ - timezone
+# - puppet
+# - chef
+# - salt-minion
+# - mcollective
+ - disable-ec2-metadata
+ - runcmd
+# - byobu
+
+# The modules that run in the 'final' stage
+cloud_final_modules:
+ - rightscale_userdata
+ - scripts-vendor
+ - scripts-per-once
+ - scripts-per-boot
+ - scripts-per-instance
+ - scripts-user
+ - ssh-authkey-fingerprints
+ - keys-to-console
+ - phone-home
+ - final-message
+ - power-state-change
+
+# System and/or distro specific settings
+# (not accessible to handlers/transforms)
+system_info:
+   distro: freebsd
+   default_user:
+# name: username and password/pubkey are obtained from ovf file
+     groups: [wheel]
+     sudo: ["ALL=(ALL) NOPASSWD:ALL"]
+     shell: /bin/csh

Follow ups