aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorbapt <bapt@FreeBSD.org>2017-05-18 04:01:10 +0800
committerbapt <bapt@FreeBSD.org>2017-05-18 04:01:10 +0800
commit1e35d245152b8ae1b2b2c79532e87d13ef7a67df (patch)
treea818d36ca8b2d1f6d33709e60860466347cccc08
parent7cb1bcc2b3f9c5fe734d57c0da721fd43f1b1bb1 (diff)
downloadfreebsd-ports-gnome-1e35d245152b8ae1b2b2c79532e87d13ef7a67df.tar.gz
freebsd-ports-gnome-1e35d245152b8ae1b2b2c79532e87d13ef7a67df.tar.zst
freebsd-ports-gnome-1e35d245152b8ae1b2b2c79532e87d13ef7a67df.zip
Add a cloud init package for FreeBSD on Azure
Submitted by: decui@microsoft.com Differential Revision: https://reviews.freebsd.org/D10566
-rw-r--r--net/Makefile1
-rw-r--r--net/cloud-init-azure/Makefile57
-rw-r--r--net/cloud-init-azure/distinfo3
-rw-r--r--net/cloud-init-azure/files/patch-frbsd-azure.txt1213
-rw-r--r--net/cloud-init-azure/pkg-descr3
-rw-r--r--net/cloud-init-azure/pkg-message7
6 files changed, 1284 insertions, 0 deletions
diff --git a/net/Makefile b/net/Makefile
index 04ec877886b6..4c8071ff1fee 100644
--- a/net/Makefile
+++ b/net/Makefile
@@ -73,6 +73,7 @@
SUBDIR += citrix_ica
SUBDIR += cjdns
SUBDIR += cloud-init
+ SUBDIR += cloud-init-azure
SUBDIR += clusterit
SUBDIR += cnd
SUBDIR += coda6_client
diff --git a/net/cloud-init-azure/Makefile b/net/cloud-init-azure/Makefile
new file mode 100644
index 000000000000..2a16db74708e
--- /dev/null
+++ b/net/cloud-init-azure/Makefile
@@ -0,0 +1,57 @@
+# $FreeBSD$
+
+PORTNAME= cloud-init
+PORTVERSION= 0.7.9
+CATEGORIES= net python
+MASTER_SITES= http://launchpad.net/${PORTNAME}/trunk/${PORTVERSION}/+download/
+PKGNAMEPREFIX= ${PYTHON_PKGNAMEPREFIX}
+PKGNAMESUFFIX?= -azure
+
+MAINTAINER= honzhan@microsoft.com
+COMMENT= Init scripts for use on cloud images
+
+LICENSE= GPLv3
+LICENSE_FILE= ${WRKSRC}/LICENSE
+
+RUN_DEPENDS= dmidecode>0:sysutils/dmidecode \
+ e2fsprogs>0:sysutils/e2fsprogs \
+ python>0:lang/python \
+ ${PYTHON_PKGNAMEPREFIX}boto>0:devel/py-boto \
+ ${PYTHON_PKGNAMEPREFIX}Jinja2>0:devel/py-Jinja2 \
+ ${PYTHON_PKGNAMEPREFIX}cheetah>0:devel/py-cheetah \
+ ${PYTHON_PKGNAMEPREFIX}prettytable>0:devel/py-prettytable \
+ ${PYTHON_PKGNAMEPREFIX}configobj>0:devel/py-configobj \
+ ${PYTHON_PKGNAMEPREFIX}yaml>0:devel/py-yaml \
+ ${PYTHON_PKGNAMEPREFIX}six>0:devel/py-six \
+ ${PYTHON_PKGNAMEPREFIX}serial>0:comms/py-serial \
+ ${PYTHON_PKGNAMEPREFIX}requests>0:www/py-requests \
+ ${PYTHON_PKGNAMEPREFIX}oauthlib>0:security/py-oauthlib \
+ ${PYTHON_PKGNAMEPREFIX}jsonpatch>0:devel/py-jsonpatch \
+ ${PYTHON_PKGNAMEPREFIX}jsonpointer>0:devel/py-jsonpointer
+
+ETCDIR= ${PREFIX}/etc/cloud
+
+USES= python:2.7 shebangfix
+SHEBANG_FILES= tools/validate-yaml.py tools/read-dependencies \
+ tools/read-version tools/hacking.py
+USE_PYTHON= autoplist distutils
+
+PYDISTUTILS_INSTALLARGS+= "--init-system=sysvinit_freebsd"
+
+ONLY_FOR_ARCHS= amd64 i386
+ONLY_FOR_ARCHS_REASON= currently depends on dmidecode which is x86-only
+
+PLIST_DIRS= /var/lib/cloud
+
+post-patch:
+ ${REINPLACE_CMD} -e "s,/usr/local,${PREFIX},g" ${WRKSRC}/setup.py
+ ${REINPLACE_CMD} -e "s,/etc/,${PREFIX}/etc/,g" \
+ ${WRKSRC}/cloudinit/settings.py
+
+post-build:
+ @cd ${WRKSRC} ; ${MV} config/cloud.cfg-freebsd config/cloud.cfg
+
+post-install:
+ ${MKDIR} ${STAGEDIR}/var/lib/cloud
+
+.include <bsd.port.mk>
diff --git a/net/cloud-init-azure/distinfo b/net/cloud-init-azure/distinfo
new file mode 100644
index 000000000000..43fed1b96c0c
--- /dev/null
+++ b/net/cloud-init-azure/distinfo
@@ -0,0 +1,3 @@
+TIMESTAMP = 1495051158
+SHA256 (cloud-init-0.7.9.tar.gz) = 76edb80bf1bdbda68f8014bc057a303ae438a139bdf394e825e548d6ae39d472
+SIZE (cloud-init-0.7.9.tar.gz) = 602188
diff --git a/net/cloud-init-azure/files/patch-frbsd-azure.txt b/net/cloud-init-azure/files/patch-frbsd-azure.txt
new file mode 100644
index 000000000000..fe6e72cba422
--- /dev/null
+++ b/net/cloud-init-azure/files/patch-frbsd-azure.txt
@@ -0,0 +1,1213 @@
+--- cloudinit/config/cc_resizefs.py.orig 2016-12-23 16:37:45 UTC
++++ cloudinit/config/cc_resizefs.py
+@@ -33,7 +33,10 @@ disabled altogether by setting ``resize_rootfs`` to ``
+ """
+
+ import errno
++import getopt
+ import os
++import re
++import shlex
+ import stat
+
+ from cloudinit.settings import PER_ALWAYS
+@@ -58,6 +61,62 @@ def _resize_ufs(mount_point, devpth):
+ return ('growfs', devpth)
+
+
++def _get_dumpfs_output(mount_point):
++ dumpfs_res, err = util.subp(['dumpfs', '-m', mount_point])
++ return dumpfs_res
++
++
++def _get_gpart_output(part):
++ gpart_res, err = util.subp(['gpart', 'show', part])
++ return gpart_res
++
++
++def _can_skip_resize_ufs(mount_point, devpth):
++ # extract the current fs sector size
++ """
++ # dumpfs -m /
++ # newfs command for / (/dev/label/rootfs)
++ newfs -O 2 -U -a 4 -b 32768 -d 32768 -e 4096 -f 4096 -g 16384
++ -h 64 -i 8192 -j -k 6408 -m 8 -o time -s 58719232 /dev/label/rootf
++ """
++ cur_fs_sz = None
++ frag_sz = None
++ dumpfs_res = _get_dumpfs_output(mount_point)
++ for line in dumpfs_res.splitlines():
++ if not line.startswith('#'):
++ newfs_cmd = shlex.split(line)
++ opt_value = 'O:Ua:s:b:d:e:f:g:h:i:jk:m:o:'
++ optlist, args = getopt.getopt(newfs_cmd[1:], opt_value)
++ for o, a in optlist:
++ if o == "-s":
++ cur_fs_sz = int(a)
++ if o == "-f":
++ frag_sz = int(a)
++ # check the current partition size
++ """
++ # gpart show /dev/da0
++=> 40 62914480 da0 GPT (30G)
++ 40 1024 1 freebsd-boot (512K)
++ 1064 58719232 2 freebsd-ufs (28G)
++ 58720296 3145728 3 freebsd-swap (1.5G)
++ 61866024 1048496 - free - (512M)
++ """
++ expect_sz = None
++ m = re.search('^(/dev/.+)p([0-9])$', devpth)
++ gpart_res = _get_gpart_output(m.group(1))
++ for line in gpart_res.splitlines():
++ if re.search(r"freebsd-ufs", line):
++ fields = line.split()
++ expect_sz = int(fields[1])
++ # Normalize the gpart sector size,
++ # because the size is not exactly the same as fs size.
++ normal_expect_sz = (expect_sz - expect_sz % (frag_sz / 512))
++ if normal_expect_sz == cur_fs_sz:
++ return True
++ else:
++ return False
++
++
+ # Do not use a dictionary as these commands should be able to be used
+ # for multiple filesystem types if possible, e.g. one command for
+ # ext2, ext3 and ext4.
+@@ -68,6 +127,10 @@ RESIZE_FS_PREFIXES_CMDS = [
+ ('ufs', _resize_ufs),
+ ]
+
++RESIZE_FS_PRECHECK_CMDS = {
++ 'ufs': _can_skip_resize_ufs
++}
++
+ NOBLOCK = "noblock"
+
+
+@@ -90,6 +153,14 @@ def rootdev_from_cmdline(cmdline):
+ return "/dev/" + found
+
+
++def can_skip_resize(fs_type, resize_what, devpth):
++ fstype_lc = fs_type.lower()
++ for i, func in RESIZE_FS_PRECHECK_CMDS.items():
++ if fstype_lc.startswith(i):
++ return func(resize_what, devpth)
++ return False
++
++
+ def handle(name, cfg, _cloud, log, args):
+ if len(args) != 0:
+ resize_root = args[0]
+@@ -158,6 +229,11 @@ def handle(name, cfg, _cloud, log, args):
+ return
+
+ resizer = None
++ if can_skip_resize(fs_type, resize_what, devpth):
++ log.debug("Skip resize filesystem type %s for %s",
++ fs_type, resize_what)
++ return
++
+ fstype_lc = fs_type.lower()
+ for (pfix, root_cmd) in RESIZE_FS_PREFIXES_CMDS:
+ if fstype_lc.startswith(pfix):
+--- cloudinit/distros/__init__.py.orig 2016-12-23 16:37:45 UTC
++++ 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
+--- cloudinit/distros/freebsd.py.orig 2016-12-23 16:37:45 UTC
++++ 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)
+@@ -38,6 +39,8 @@ class Distro(distros.Distro):
+ # should only happen say once per instance...)
+ self._runner = helpers.Runners(paths)
+ self.osfamily = 'freebsd'
++ self.ipv4_pat = re.compile(r"\s+inet\s+\d+[.]\d+[.]\d+[.]\d+")
++ cfg['ssh_svcname'] = 'sshd'
+
+ # Updates a key in /etc/rc.conf.
+ def updatercconf(self, key, value):
+@@ -183,7 +186,6 @@ class Distro(distros.Distro):
+ "gecos": '-c',
+ "primary_group": '-g',
+ "groups": '-G',
+- "passwd": '-h',
+ "shell": '-s',
+ "inactive": '-E',
+ }
+@@ -193,19 +195,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 +220,21 @@ 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 is not None:
++ self.set_passwd(name, passwd_val, hashed=True)
+
+ def set_passwd(self, user, passwd, hashed=False):
+- cmd = ['pw', 'usermod', user]
+-
+ if hashed:
+- cmd.append('-H')
++ hash_opt = "-H"
+ else:
+- cmd.append('-h')
++ hash_opt = "-h"
+
+- cmd.append('0')
+-
+ try:
+- util.subp(cmd, passwd, logstring="chpasswd for %s" % user)
++ util.subp(['pw', 'usermod', user, hash_opt, '0'],
++ data=passwd, logstring="chpasswd for %s" % user)
+ except Exception as e:
+ util.logexc(LOG, "Failed to set password for %s", user)
+ raise e
+@@ -270,6 +266,255 @@ class Distro(distros.Distro):
+ if 'ssh_authorized_keys' in kwargs:
+ keys = set(kwargs['ssh_authorized_keys']) or []
+ ssh_util.setup_user_keys(keys, name, options=None)
++
++ @staticmethod
++ def get_ifconfig_list():
++ cmd = ['ifconfig', '-l']
++ (nics, err) = util.subp(cmd, rcs=[0, 1])
++ if len(err):
++ LOG.warn("Error running %s: %s", cmd, err)
++ return None
++ return nics
++
++ @staticmethod
++ def get_ifconfig_ifname_out(ifname):
++ cmd = ['ifconfig', ifname]
++ (if_result, err) = util.subp(cmd, rcs=[0, 1])
++ if len(err):
++ LOG.warn("Error running %s: %s", cmd, err)
++ return None
++ return if_result
++
++ @staticmethod
++ def get_ifconfig_ether():
++ cmd = ['ifconfig', '-l', 'ether']
++ (nics, err) = util.subp(cmd, rcs=[0, 1])
++ if len(err):
++ LOG.warn("Error running %s: %s", cmd, err)
++ return None
++ return nics
++
++ @staticmethod
++ def get_interface_mac(ifname):
++ if_result = Distro.get_ifconfig_ifname_out(ifname)
++ for item in if_result.splitlines():
++ if item.find('ether ') != -1:
++ mac = str(item.split()[1])
++ if mac:
++ return mac
++
++ @staticmethod
++ def get_devicelist():
++ nics = Distro.get_ifconfig_list()
++ return nics.split()
++
++ @staticmethod
++ def get_ipv6():
++ ipv6 = []
++ nics = Distro.get_devicelist()
++ for nic in nics:
++ if_result = Distro.get_ifconfig_ifname_out(nic)
++ for item in if_result.splitlines():
++ if item.find("inet6 ") != -1 and item.find("scopeid") == -1:
++ ipv6.append(nic)
++ return ipv6
++
++ def get_ipv4(self):
++ ipv4 = []
++ nics = Distro.get_devicelist()
++ for nic in nics:
++ if_result = Distro.get_ifconfig_ifname_out(nic)
++ for item in if_result.splitlines():
++ print(item)
++ if self.ipv4_pat.match(item):
++ ipv4.append(nic)
++ return ipv4
++
++ def is_up(self, ifname):
++ if_result = Distro.get_ifconfig_ifname_out(ifname)
++ pat = "^" + ifname
++ for item in if_result.splitlines():
++ 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 = Distro.get_devicelist()
++ bymac = {}
++ for n in names:
++ bymac[Distro.get_interface_mac(n)] = {
++ 'name': n, 'up': self.is_up(n), 'downable': None}
++
++ if check_downable:
++ 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)
++
++ @classmethod
++ def generate_fallback_config(self):
++ nics = Distro.get_ifconfig_ether()
++ if nics is None:
++ LOG.debug("Fail to get network interfaces")
++ return None
++ potential_interfaces = nics.split()
++ connected = []
++ for nic in potential_interfaces:
++ pat = "^" + nic
++ if_result = Distro.get_ifconfig_ifname_out(nic)
++ 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 = Distro.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 = Distro.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)
+--- cloudinit/settings.py.orig 2016-12-23 16:37:45 UTC
++++ 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',
+--- cloudinit/sources/DataSourceAzure.py.orig 2016-12-23 16:37:45 UTC
++++ cloudinit/sources/DataSourceAzure.py
+@@ -10,6 +10,7 @@ import crypt
+ from functools import partial
+ import os
+ import os.path
++import re
+ import time
+ from xml.dom import minidom
+ import xml.etree.ElementTree as ET
+@@ -32,19 +33,160 @@ 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 find_storvscid_from_sysctl_pnpinfo(sysctl_out, deviceid):
++ # extract the 'X' from dev.storvsc.X. if deviceid matches
++ """
++ dev.storvsc.1.%pnpinfo:
++ classid=32412632-86cb-44a2-9b5c-50d1417354f5
++ deviceid=00000000-0001-8899-0000-000000000000
++ """
++ for line in sysctl_out.splitlines():
++ if re.search(r"pnpinfo", line):
++ fields = line.split()
++ if len(fields) >= 3:
++ columns = fields[2].split('=')
++ if (len(columns) >= 2 and
++ columns[0] == "deviceid" and
++ columns[1].startswith(deviceid)):
++ comps = fields[0].split('.')
++ return comps[2]
++ return None
++
++
++def find_busdev_from_disk(camcontrol_out, disk_drv):
++ # find the scbusX from 'camcontrol devlist -b' output
++ # if disk_drv matches the specified disk driver, i.e. blkvsc1
++ """
++ scbus0 on ata0 bus 0
++ scbus1 on ata1 bus 0
++ scbus2 on blkvsc0 bus 0
++ scbus3 on blkvsc1 bus 0
++ scbus4 on storvsc2 bus 0
++ scbus5 on storvsc3 bus 0
++ scbus-1 on xpt0 bus 0
++ """
++ for line in camcontrol_out.splitlines():
++ if re.search(disk_drv, line):
++ items = line.split()
++ return items[0]
++ return None
++
++
++def find_dev_from_busdev(camcontrol_out, busdev):
++ # find the daX from 'camcontrol devlist' output
++ # if busdev matches the specified value, i.e. 'scbus2'
++ """
++ <Msft Virtual CD/ROM 1.0> at scbus1 target 0 lun 0 (cd0,pass0)
++ <Msft Virtual Disk 1.0> at scbus2 target 0 lun 0 (da0,pass1)
++ <Msft Virtual Disk 1.0> at scbus3 target 1 lun 0 (da1,pass2)
++ """
++ for line in camcontrol_out.splitlines():
++ if re.search(busdev, line):
++ items = line.split('(')
++ if len(items) == 2:
++ dev_pass = items[1].split(',')
++ return dev_pass[0]
++ return None
++
++
++def get_dev_storvsc_sysctl():
++ try:
++ sysctl_out, err = util.subp(['sysctl', 'dev.storvsc'])
++ except util.ProcessExecutionError:
++ LOG.debug("Fail to execute sysctl dev.storvsc")
++ return None
++ return sysctl_out
++
++
++def get_camcontrol_dev_bus():
++ try:
++ camcontrol_b_out, err = util.subp(['camcontrol', 'devlist', '-b'])
++ except util.ProcessExecutionError:
++ LOG.debug("Fail to execute camcontrol devlist -b")
++ return None
++ return camcontrol_b_out
++
++
++def get_camcontrol_dev():
++ try:
++ camcontrol_out, err = util.subp(['camcontrol', 'devlist'])
++ except util.ProcessExecutionError:
++ LOG.debug("Fail to execute camcontrol devlist")
++ return None
++ return camcontrol_out
++
++
++def get_resource_disk_on_freebsd(port_id):
++ g0 = "00000000"
++ if port_id > 1:
++ g0 = "00000001"
++ port_id = port_id - 2
++ 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'
++ """
++ sysctl_out = get_dev_storvsc_sysctl()
++
++ storvscid = find_storvscid_from_sysctl_pnpinfo(sysctl_out, g0g1)
++ if not storvscid:
++ LOG.debug("Fail to find storvsc id from sysctl")
++ return None
++
++ camcontrol_b_out = get_camcontrol_dev_bus()
++ camcontrol_out = get_camcontrol_dev()
++ # try to find /dev/XX from 'blkvsc' device
++ blkvsc = "blkvsc{0}".format(storvscid)
++ scbusx = find_busdev_from_disk(camcontrol_b_out, blkvsc)
++ if scbusx:
++ devname = find_dev_from_busdev(camcontrol_out, scbusx)
++ if devname is None:
++ LOG.debug("Fail to find /dev/daX")
++ return None
++ return devname
++ # try to find /dev/XX from 'storvsc' device
++ storvsc = "storvsc{0}".format(storvscid)
++ scbusx = find_busdev_from_disk(camcontrol_b_out, storvsc)
++ if scbusx:
++ devname = find_dev_from_busdev(camcontrol_out, scbusx)
++ if devname is None:
++ LOG.debug("Fail to find /dev/daX")
++ return None
++ return devname
++ 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 is not 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 +195,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 +320,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 +352,13 @@ 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. 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])
+@@ -619,8 +767,19 @@ 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"
++ try:
++ util.subp(["mount", "-o", "ro", "-t", "udf", cdrom_dev,
++ "/mnt/cdrom/secure"])
++ except util.ProcessExecutionError:
++ LOG.debug("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
+--- cloudinit/sources/helpers/azure.py.orig 2016-12-23 16:37:45 UTC
++++ cloudinit/sources/helpers/azure.py
+@@ -29,6 +29,14 @@ def cd(newdir):
+ os.chdir(prevdir)
+
+
++def get_azure_endpoint():
++ if util.is_FreeBSD():
++ azure_endpoint = "option-245"
++ else:
++ azure_endpoint = "unknown-245"
++ return azure_endpoint
++
++
+ class AzureEndpointHttpClient(object):
+
+ headers = {
+@@ -236,7 +244,8 @@ 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:
++ azure_endpoint = get_azure_endpoint()
++ 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"'))
+--- cloudinit/stages.py.orig 2016-12-23 16:37:45 UTC
++++ 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()
+--- cloudinit/util.py.orig 2016-12-23 16:37:45 UTC
++++ cloudinit/util.py
+@@ -565,6 +565,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
+@@ -2091,11 +2095,56 @@ def parse_mtab(path):
+ return None
+
+
++def find_freebsd_part(label_part):
++ if label_part.startswith("/dev/label/"):
++ target_label = label_part[5:]
++ (label_part, err) = subp(['glabel', 'status', '-s'])
++ 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 label_part
++
++
++def get_path_dev_freebsd(path, mnt_list):
++ path_found = None
++ for line in mnt_list.split("\n"):
++ items = line.split()
++ if (len(items) > 2 and os.path.exists(items[1] + path)):
++ path_found = line
++ break
++ return path_found
++
++
++def get_mount_info_freebsd(path, log=LOG):
++ (result, err) = subp(['mount', '-p', path], rcs=[0, 1])
++ if len(err):
++ # find a path if the input is not a mounting point
++ (mnt_list, err) = subp(['mount', '-p'])
++ path_found = get_path_dev_freebsd(path, mnt_list)
++ if (path_found is None):
++ return None
++ result = path_found
++ ret = result.split()
++ label_part = find_freebsd_part(ret[0])
++ return "/dev/" + label_part, ret[2], ret[1]
++
++
+ def parse_mount(path):
+ (mountoutput, _err) = subp("mount")
+ mount_locs = mountoutput.splitlines()
+ for line in mount_locs:
+ m = re.search(r'^(/dev/[\S]+) on (/.*) \((.+), .+, (.+)\)$', line)
++ if not m:
++ continue
++ # check whether the dev refers to a label on FreeBSD
++ # for example, if dev is '/dev/label/rootfs', we should
++ # continue finding the real device like '/dev/da0'.
++ devm = re.search('^(/dev/.+)p([0-9])$', m.group(1))
++ if (not devm and is_FreeBSD()):
++ return get_mount_info_freebsd(path)
+ devpth = m.group(1)
+ mount_point = m.group(2)
+ fs_type = m.group(3)
+@@ -2357,7 +2406,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
+
+--- config/cloud.cfg-freebsd.orig 2016-12-23 16:37:45 UTC
++++ config/cloud.cfg-freebsd
+@@ -5,7 +5,7 @@ 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', 'OpenStack', 'Ec2']
++datasource_list: ['ConfigDrive', 'Azure', 'OpenStack', 'Ec2']
+
+ # 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'
+--- requirements.txt.orig 2016-12-23 16:37:45 UTC
++++ requirements.txt
+@@ -28,7 +28,7 @@ configobj>=5.0.2
+ pyyaml
+
+ # The new main entrypoint uses argparse instead of optparse
+-argparse
++# argparse
+
+ # Requests handles ssl correctly!
+ requests
+--- setup.py.orig 2016-12-23 16:37:45 UTC
++++ setup.py
+@@ -87,9 +87,9 @@ ETC = "/etc"
+ USR_LIB_EXEC = "/usr/lib"
+ LIB = "/lib"
+ if os.uname()[0] == 'FreeBSD':
++ ETC = "/usr/local/etc"
+ USR = "/usr/local"
+ USR_LIB_EXEC = "/usr/local/lib"
+- ETC = "/usr/local/etc"
+ elif os.path.isfile('/etc/redhat-release'):
+ USR_LIB_EXEC = "/usr/libexec"
+
+@@ -166,8 +166,6 @@ else:
+ (ETC + '/cloud', glob('config/*.cfg')),
+ (ETC + '/cloud/cloud.cfg.d', glob('config/cloud.cfg.d/*')),
+ (ETC + '/cloud/templates', glob('templates/*')),
+- (ETC + '/NetworkManager/dispatcher.d/', ['tools/hook-network-manager']),
+- (ETC + '/dhcp/dhclient-exit-hooks.d/', ['tools/hook-dhclient']),
+ (USR_LIB_EXEC + '/cloud-init', ['tools/uncloud-init',
+ 'tools/write-ssh-key-fingerprints']),
+ (USR + '/share/doc/cloud-init', [f for f in glob('doc/*') if is_f(f)]),
+@@ -175,8 +173,13 @@ else:
+ [f for f in glob('doc/examples/*') if is_f(f)]),
+ (USR + '/share/doc/cloud-init/examples/seed',
+ [f for f in glob('doc/examples/seed/*') if is_f(f)]),
+- (LIB + '/udev/rules.d', [f for f in glob('udev/*.rules')]),
+ ]
++ if os.uname()[0] != 'FreeBSD':
++ data_files.append([
++ (ETC + '/NetworkManager/dispatcher.d/', ['tools/hook-network-manager']),
++ (ETC + '/dhcp/dhclient-exit-hooks.d/', ['tools/hook-dhclient']),
++ (LIB + '/udev/rules.d', [f for f in glob('udev/*.rules')]),
++ ])
+ # Use a subclass for install that handles
+ # adding on the right init system configuration files
+ cmdclass = {
+@@ -187,6 +190,9 @@ else:
+ requirements = read_requires()
+ if sys.version_info < (3,):
+ requirements.append('cheetah')
++if ((sys.version_info.major == 2 and sys.version_info.minor < 7) or
++ (sys.version_info.major == 3 and sys.version_info.minor < 2)):
++ requirements.append('argparse')
+
+ setuptools.setup(
+ name='cloud-init',
+--- sysvinit/freebsd/cloudconfig.orig 2016-12-23 16:37:45 UTC
++++ sysvinit/freebsd/cloudconfig
+@@ -7,23 +7,13 @@
+ . /etc/rc.subr
+
+ PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
+-export CLOUD_CFG=/usr/local/etc/cloud/cloud.cfg
+
+ name="cloudconfig"
+ command="/usr/local/bin/cloud-init"
+ start_cmd="cloudconfig_start"
+ stop_cmd=":"
+ rcvar="cloudinit_enable"
+-start_precmd="cloudinit_override"
+ start_cmd="cloudconfig_start"
+-
+-cloudinit_override()
+-{
+- # If there exist sysconfig/defaults variable override files use it...
+- if [ -f /etc/defaults/cloud-init ]; then
+- . /etc/defaults/cloud-init
+- fi
+-}
+
+ cloudconfig_start()
+ {
+--- sysvinit/freebsd/cloudfinal.orig 2016-12-23 16:37:45 UTC
++++ sysvinit/freebsd/cloudfinal
+@@ -7,23 +7,13 @@
+ . /etc/rc.subr
+
+ PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
+-export CLOUD_CFG=/usr/local/etc/cloud/cloud.cfg
+
+ name="cloudfinal"
+ command="/usr/local/bin/cloud-init"
+ start_cmd="cloudfinal_start"
+ stop_cmd=":"
+ rcvar="cloudinit_enable"
+-start_precmd="cloudinit_override"
+ start_cmd="cloudfinal_start"
+-
+-cloudinit_override()
+-{
+- # If there exist sysconfig/defaults variable override files use it...
+- if [ -f /etc/defaults/cloud-init ]; then
+- . /etc/defaults/cloud-init
+- fi
+-}
+
+ cloudfinal_start()
+ {
+--- sysvinit/freebsd/cloudinit.orig 2016-12-23 16:37:45 UTC
++++ sysvinit/freebsd/cloudinit
+@@ -7,23 +7,13 @@
+ . /etc/rc.subr
+
+ PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
+-export CLOUD_CFG=/usr/local/etc/cloud/cloud.cfg
+
+ name="cloudinit"
+ command="/usr/local/bin/cloud-init"
+ start_cmd="cloudinit_start"
+ stop_cmd=":"
+ rcvar="cloudinit_enable"
+-start_precmd="cloudinit_override"
+ start_cmd="cloudinit_start"
+-
+-cloudinit_override()
+-{
+- # If there exist sysconfig/defaults variable override files use it...
+- if [ -f /etc/defaults/cloud-init ]; then
+- . /etc/defaults/cloud-init
+- fi
+-}
+
+ cloudinit_start()
+ {
+--- sysvinit/freebsd/cloudinitlocal.orig 2016-12-23 16:37:45 UTC
++++ sysvinit/freebsd/cloudinitlocal
+@@ -1,29 +1,19 @@
+ #!/bin/sh
+
+ # PROVIDE: cloudinitlocal
+-# REQUIRE: mountcritlocal
++# REQUIRE: ldconfig mountcritlocal
+ # BEFORE: NETWORKING FILESYSTEMS cloudinit cloudconfig cloudfinal
+
+ . /etc/rc.subr
+
+ PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
+-export CLOUD_CFG=/usr/local/etc/cloud/cloud.cfg
+
+ name="cloudinitlocal"
+ command="/usr/local/bin/cloud-init"
+ start_cmd="cloudlocal_start"
+ stop_cmd=":"
+ rcvar="cloudinit_enable"
+-start_precmd="cloudinit_override"
+ start_cmd="cloudlocal_start"
+-
+-cloudinit_override()
+-{
+- # If there exist sysconfig/defaults variable override files use it...
+- if [ -f /etc/defaults/cloud-init ]; then
+- . /etc/defaults/cloud-init
+- fi
+-}
+
+ cloudlocal_start()
+ {
+--- tests/unittests/test_datasource/test_azure.py.orig 2016-12-23 16:37:45 UTC
++++ tests/unittests/test_datasource/test_azure.py
+@@ -3,6 +3,8 @@
+ from cloudinit import helpers
+ from cloudinit.util import b64e, decode_binary, load_file
+ from cloudinit.sources import DataSourceAzure
++from cloudinit.util import find_freebsd_part
++from cloudinit.util import get_path_dev_freebsd
+
+ from ..helpers import TestCase, populate_dir, mock, ExitStack, PY26, SkipTest
+
+@@ -95,6 +97,41 @@ class TestAzureDataSource(TestCase):
+ for module, name, new in patches:
+ self.patches.enter_context(mock.patch.object(module, name, new))
+
++ def _get_mockds(self):
++ mod = DataSourceAzure
++ sysctl_out = "dev.storvsc.3.%pnpinfo: "\
++ "classid=ba6163d9-04a1-4d29-b605-72e2ffb1dc7f "\
++ "deviceid=f8b3781b-1e82-4818-a1c3-63d806ec15bb\n"
++ sysctl_out += "dev.storvsc.2.%pnpinfo: "\
++ "classid=ba6163d9-04a1-4d29-b605-72e2ffb1dc7f "\
++ "deviceid=f8b3781a-1e82-4818-a1c3-63d806ec15bb\n"
++ sysctl_out += "dev.storvsc.1.%pnpinfo: "\
++ "classid=32412632-86cb-44a2-9b5c-50d1417354f5 "\
++ "deviceid=00000000-0001-8899-0000-000000000000\n"
++ camctl_devbus = """
++scbus0 on ata0 bus 0
++scbus1 on ata1 bus 0
++scbus2 on blkvsc0 bus 0
++scbus3 on blkvsc1 bus 0
++scbus4 on storvsc2 bus 0
++scbus5 on storvsc3 bus 0
++scbus-1 on xpt0 bus 0
++ """
++ camctl_dev = """
++<Msft Virtual CD/ROM 1.0> at scbus1 target 0 lun 0 (cd0,pass0)
++<Msft Virtual Disk 1.0> at scbus2 target 0 lun 0 (da0,pass1)
++<Msft Virtual Disk 1.0> at scbus3 target 1 lun 0 (da1,pass2)
++ """
++ self.apply_patches([
++ (mod, 'get_dev_storvsc_sysctl', mock.MagicMock(
++ return_value=sysctl_out)),
++ (mod, 'get_camcontrol_dev_bus', mock.MagicMock(
++ return_value=camctl_devbus)),
++ (mod, 'get_camcontrol_dev', mock.MagicMock(
++ return_value=camctl_dev))
++ ])
++ return mod
++
+ def _get_ds(self, data, agent_command=None):
+
+ def dsdevs():
+@@ -176,6 +213,34 @@ class TestAzureDataSource(TestCase):
+ except AssertionError:
+ return
+ raise AssertionError("XML is the same")
++
++ def test_get_resource_disk(self):
++ ds = self._get_mockds()
++ dev = ds.get_resource_disk_on_freebsd(1)
++ self.assertEqual("da1", dev)
++
++ @mock.patch('cloudinit.util.subp')
++ def test_find_freebsd_part_on_Azure(self, mock_subp):
++ glabel_out = '''
++gptid/fa52d426-c337-11e6-8911-00155d4c5e47 N/A da0p1
++ label/rootfs N/A da0p2
++ label/swap N/A da0p3
++'''
++ mock_subp.return_value = (glabel_out, "")
++ res = find_freebsd_part("/dev/label/rootfs")
++ self.assertEqual("da0p2", res)
++
++ def test_get_path_dev_freebsd_on_Azure(self):
++ mnt_list = '''
++/dev/label/rootfs / ufs rw 1 1
++devfs /dev devfs rw,multilabel 0 0
++fdescfs /dev/fd fdescfs rw 0 0
++/dev/da1s1 /mnt/resource ufs rw 2 2
++'''
++ with mock.patch.object(os.path, 'exists',
++ return_value=True):
++ res = get_path_dev_freebsd('/etc', mnt_list)
++ self.assertNotEqual(res, None)
+
+ def test_basic_seed_dir(self):
+ odata = {'HostName': "myhost", 'UserName': "myuser"}
+--- tests/unittests/test_datasource/test_azure_helper.py.orig 2016-12-23 16:37:45 UTC
++++ tests/unittests/test_datasource/test_azure_helper.py
+@@ -72,10 +72,11 @@ class TestFindEndpoint(TestCase):
+
+ @staticmethod
+ def _build_lease_content(encoded_address):
++ endpoint = azure_helper.get_azure_endpoint()
+ return '\n'.join([
+ 'lease {',
+ ' interface "eth0";',
+- ' option unknown-245 {0};'.format(encoded_address),
++ ' option {0} {1};'.format(endpoint, encoded_address),
+ '}'])
+
+ def test_from_dhcp_client(self):
+--- tests/unittests/test_datasource/test_cloudstack.py.orig 2016-12-23 16:37:45 UTC
++++ tests/unittests/test_datasource/test_cloudstack.py
+@@ -15,6 +15,11 @@ class TestCloudStackPasswordFetching(TestCase):
+ mod_name = 'cloudinit.sources.DataSourceCloudStack'
+ self.patches.enter_context(mock.patch('{0}.ec2'.format(mod_name)))
+ self.patches.enter_context(mock.patch('{0}.uhelp'.format(mod_name)))
++ default_gw = "192.201.20.0"
++ mod_name = 'cloudinit.sources.DataSourceCloudStack.get_default_gateway'
++ get_default_gw = mock.MagicMock(return_value=default_gw)
++ self.patches.enter_context(
++ mock.patch(mod_name, get_default_gw))
+
+ def _set_password_server_response(self, response_string):
+ subp = mock.MagicMock(return_value=(response_string, ''))
+--- tests/unittests/test_distros/test_netconfig.py.orig 2016-12-23 16:37:45 UTC
++++ tests/unittests/test_distros/test_netconfig.py
+@@ -83,6 +83,20 @@ class WriteBuffer(object):
+
+ class TestNetCfgDistro(TestCase):
+
++ frbsd_ifout = """\
++hn0: flags=8843<UP,BROADCAST,RUNNING,SIMPLEX,MULTICAST> metric 0 mtu 1500
++ options=51b<RXCSUM,TXCSUM,VLAN_MTU,VLAN_HWTAGGING,TSO4,LRO>
++ ether 00:15:5d:4c:73:00
++ inet6 fe80::215:5dff:fe4c:7300%hn0 prefixlen 64 scopeid 0x2
++ inet 10.156.76.127 netmask 0xfffffc00 broadcast 10.156.79.255
++ nd6 options=23<PERFORMNUD,ACCEPT_RTADV,AUTO_LINKLOCAL>
++ media: Ethernet autoselect (10Gbase-T <full-duplex>)
++ status: active
++"""
++
++ def setUp(self):
++ super(TestNetCfgDistro, self).setUp()
++
+ def _get_distro(self, dname):
+ cls = distros.fetch(dname)
+ cfg = settings.CFG_BUILTIN
+@@ -126,6 +140,29 @@ class TestNetCfgDistro(TestCase):
+ self.assertIn(k, b1)
+ for (k, v) in b1.items():
+ self.assertEqual(v, b2[k])
++
++ @mock.patch('cloudinit.distros.freebsd.Distro.get_ifconfig_list')
++ @mock.patch('cloudinit.distros.freebsd.Distro.get_ifconfig_ifname_out')
++ def test_get_ip_nic_freebsd(self, ifname_out, iflist):
++ frbsd_distro = self._get_distro('freebsd')
++ iflist.return_value = "lo0 hn0"
++ ifname_out.return_value = self.frbsd_ifout
++ res = frbsd_distro.get_ipv4()
++ self.assertEqual(res, ['lo0', 'hn0'])
++ res = frbsd_distro.get_ipv6()
++ self.assertEqual(res, [])
++
++ @mock.patch('cloudinit.distros.freebsd.Distro.get_ifconfig_ether')
++ @mock.patch('cloudinit.distros.freebsd.Distro.get_ifconfig_ifname_out')
++ @mock.patch('cloudinit.distros.freebsd.Distro.get_interface_mac')
++ def test_generate_fallback_config_freebsd(self, mac, ifname_out, if_ether):
++ frbsd_distro = self._get_distro('freebsd')
++
++ if_ether.return_value = 'hn0'
++ ifname_out.return_value = self.frbsd_ifout
++ mac.return_value = '00:15:5d:4c:73:00'
++ res = frbsd_distro.generate_fallback_config()
++ self.assertIsNotNone(res)
+
+ def test_simple_write_rh(self):
+ rh_distro = self._get_distro('rhel')
+--- tests/unittests/test_util.py.orig 2016-12-23 16:37:45 UTC
++++ tests/unittests/test_util.py
+@@ -567,7 +567,8 @@ class TestSubp(helpers.TestCase):
+ def test_subp_capture_stderr(self):
+ data = b'hello world'
+ (out, err) = util.subp(self.stdin2err, capture=True,
+- decode=False, data=data)
++ decode=False, data=data,
++ update_env={'LC_ALL': 'C'})
+ self.assertEqual(err, data)
+ self.assertEqual(out, b'')
+
+--- tools/build-on-freebsd.orig 2016-12-23 16:37:45 UTC
++++ tools/build-on-freebsd
+@@ -3,16 +3,14 @@
+ # installing cloud-init. This script takes care of building and installing. It
+ # will optionally make a first run at the end.
+
+-fail() { echo "FAILED:" "$@" 1>&2; exit 1; }
++fail() { echo "FAILED:" "$@" 1>&2; exit 1;}
+
+ # Check dependencies:
+ depschecked=/tmp/c-i.dependencieschecked
+ pkgs="
+ dmidecode
+ e2fsprogs
+- gpart
+ py27-Jinja2
+- py27-argparse
+ py27-boto
+ py27-cheetah
+ py27-configobj
+@@ -38,7 +36,7 @@ python setup.py build
+ python setup.py install -O1 --skip-build --prefix /usr/local/ --init-system sysvinit_freebsd
+
+ # Install the correct config file:
+-cp config/cloud.cfg-freebsd /usr/local/etc/cloud/cloud.cfg
++cp config/cloud.cfg-freebsd /etc/cloud/cloud.cfg
+
+ # Enable cloud-init in /etc/rc.conf:
+ sed -i.bak -e "/cloudinit_enable=.*/d" /etc/rc.conf
diff --git a/net/cloud-init-azure/pkg-descr b/net/cloud-init-azure/pkg-descr
new file mode 100644
index 000000000000..14f07da5f168
--- /dev/null
+++ b/net/cloud-init-azure/pkg-descr
@@ -0,0 +1,3 @@
+Package provides configuration and customization of Azure instance.
+
+WWW: https://launchpad.net/cloud-init
diff --git a/net/cloud-init-azure/pkg-message b/net/cloud-init-azure/pkg-message
new file mode 100644
index 000000000000..8ae498bfa99e
--- /dev/null
+++ b/net/cloud-init-azure/pkg-message
@@ -0,0 +1,7 @@
+==========================================================
+To enable cloud-init, add the following line to rc.conf:
+
+cloudinit_enable="YES"
+
+This will make sure cloud-init is started at boot.
+==========================================================