cloud-init-dev team mailing list archive
-
cloud-init-dev team
-
Mailing list archive
-
Message #01528
[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