Skip site navigation (1)Skip section navigation (2)
Date:      Wed, 17 May 2017 20:01:11 +0000 (UTC)
From:      Baptiste Daroussin <bapt@FreeBSD.org>
To:        ports-committers@freebsd.org, svn-ports-all@freebsd.org, svn-ports-head@freebsd.org
Subject:   svn commit: r441117 - in head/net: . cloud-init-azure cloud-init-azure/files
Message-ID:  <201705172001.v4HK1BNr004912@repo.freebsd.org>

next in thread | raw e-mail | index | archive | help
Author: bapt
Date: Wed May 17 20:01:10 2017
New Revision: 441117
URL: https://svnweb.freebsd.org/changeset/ports/441117

Log:
  Add a cloud init package for FreeBSD on Azure
  
  Submitted by:	decui@microsoft.com
  Differential Revision:	https://reviews.freebsd.org/D10566

Added:
  head/net/cloud-init-azure/
  head/net/cloud-init-azure/Makefile   (contents, props changed)
  head/net/cloud-init-azure/distinfo   (contents, props changed)
  head/net/cloud-init-azure/files/
  head/net/cloud-init-azure/files/patch-frbsd-azure.txt   (contents, props changed)
  head/net/cloud-init-azure/pkg-descr   (contents, props changed)
  head/net/cloud-init-azure/pkg-message   (contents, props changed)
Modified:
  head/net/Makefile

Modified: head/net/Makefile
==============================================================================
--- head/net/Makefile	Wed May 17 19:46:25 2017	(r441116)
+++ head/net/Makefile	Wed May 17 20:01:10 2017	(r441117)
@@ -73,6 +73,7 @@
     SUBDIR += citrix_ica
     SUBDIR += cjdns
     SUBDIR += cloud-init
+    SUBDIR += cloud-init-azure
     SUBDIR += clusterit
     SUBDIR += cnd
     SUBDIR += coda6_client

Added: head/net/cloud-init-azure/Makefile
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ head/net/cloud-init-azure/Makefile	Wed May 17 20:01:10 2017	(r441117)
@@ -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>

Added: head/net/cloud-init-azure/distinfo
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ head/net/cloud-init-azure/distinfo	Wed May 17 20:01:10 2017	(r441117)
@@ -0,0 +1,3 @@
+TIMESTAMP = 1495051158
+SHA256 (cloud-init-0.7.9.tar.gz) = 76edb80bf1bdbda68f8014bc057a303ae438a139bdf394e825e548d6ae39d472
+SIZE (cloud-init-0.7.9.tar.gz) = 602188

Added: head/net/cloud-init-azure/files/patch-frbsd-azure.txt
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ head/net/cloud-init-azure/files/patch-frbsd-azure.txt	Wed May 17 20:01:10 2017	(r441117)
@@ -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

*** DIFF OUTPUT TRUNCATED AT 1000 LINES ***



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201705172001.v4HK1BNr004912>