← Back to team overview

cloud-init-dev team mailing list archive

[Merge] lp:~harmw/cloud-init/freebsd into lp:cloud-init

 

Harm Weites has proposed merging lp:~harmw/cloud-init/freebsd into lp:cloud-init.

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

For more details, see:
https://code.launchpad.net/~harmw/cloud-init/freebsd/+merge/231024

Several FreeBSD fixes, a full changelog can be viewed in the commitlog.

Most notable fix is in the initscripts and configuration file, needed for it to 'just work'. The current trunk is broken.

There is one unresolved issue regarding path resolving of modules (?), which is why there is one DataSource explicitly enabled in the configuration file (and working/being used). This however shouldn't affect much people, since most cloud users just run c-i on Linux :)

Once we have these fixes in place (together with a version bump), the port can be submitted to bugs.freebsd.org/bugzilla as well.
-- 
https://code.launchpad.net/~harmw/cloud-init/freebsd/+merge/231024
Your team cloud init development team is requested to review the proposed merge of lp:~harmw/cloud-init/freebsd into lp:cloud-init.
=== modified file 'cloudinit/config/cc_resizefs.py'
--- cloudinit/config/cc_resizefs.py	2014-02-05 15:36:47 +0000
+++ cloudinit/config/cc_resizefs.py	2014-08-15 20:32:20 +0000
@@ -41,7 +41,7 @@
 
 
 def _resize_ufs(mount_point, devpth):  # pylint: disable=W0613
-    return ('growfs', devpth)
+    return ('growfs', '-y', devpth)
 
 # 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

=== modified file 'cloudinit/distros/freebsd.py'
--- cloudinit/distros/freebsd.py	2014-02-28 21:40:08 +0000
+++ cloudinit/distros/freebsd.py	2014-08-15 20:32:20 +0000
@@ -26,6 +26,9 @@
 from cloudinit import ssh_util
 from cloudinit import util
 
+from cloudinit.distros import net_util
+from cloudinit.distros.parsers.resolv_conf import ResolvConf
+
 LOG = logging.getLogger(__name__)
 
 
@@ -33,6 +36,7 @@
     rc_conf_fn = "/etc/rc.conf"
     login_conf_fn = '/etc/login.conf'
     login_conf_fn_bak = '/etc/login.conf.orig'
+    resolv_conf_fn = '/etc/resolv.conf'
 
     def __init__(self, name, cfg, paths):
         distros.Distro.__init__(self, name, cfg, paths)
@@ -44,30 +48,39 @@
 
     # Updates a key in /etc/rc.conf.
     def updatercconf(self, key, value):
-        LOG.debug("updatercconf: %s => %s", key, value)
+        LOG.debug("Checking %s for: %s = %s", self.rc_conf_fn, key, value)
         conf = self.loadrcconf()
         config_changed = False
         for item in conf:
             if item == key and conf[item] != value:
                 conf[item] = value
-                LOG.debug("[rc.conf]: Value %s for key %s needs to be changed",
-                          value, key)
+                LOG.debug("Changing key in %s: %s = %s", self.rc_conf_fn, key,
+                            value)
                 config_changed = True
 
         if config_changed:
-            LOG.debug("Writing new %s file", self.rc_conf_fn)
+            LOG.info("Writing %s", self.rc_conf_fn)
             buf = StringIO()
             for keyval in conf.items():
-                buf.write("%s=%s\n" % keyval)
+                buf.write('%s="%s"\n' % keyval)
             util.write_file(self.rc_conf_fn, buf.getvalue())
 
-    # Load the contents of /etc/rc.conf and store all keys in a dict.
+    # Load the contents of /etc/rc.conf and store all keys in a dict. Make sure
+    # quotes are ignored:
+    #  hostname="bla"
     def loadrcconf(self):
         conf = {}
         lines = util.load_file(self.rc_conf_fn).splitlines()
         for line in lines:
+            if not re.match(r'^(.+)=(.+)', line):
+                LOG.debug("Skipping line from /etc/rc.conf: %s", line)
+                continue
+
+            # TODO: just use the matches please...
             tok = line.split('=')
-            conf[tok[0]] = tok[1].rstrip()
+            key = tok[0]
+            val = re.sub(r'^"|"$', '', tok[1].rstrip())
+            conf[key] = val
         return conf
 
     def readrcconf(self, key):
@@ -218,7 +231,66 @@
             ssh_util.setup_user_keys(keys, name, options=None)
 
     def _write_network(self, settings):
-        return
+        entries = net_util.translate_network(settings)
+        LOG.debug("Translated network settings")
+        LOG.debug("\n========== UBUNTU FORMAT START ==========")
+        LOG.debug("%s\n========== UBUNTU FORMAT END ==========", settings)
+        LOG.debug("\n========== GENERIC FORMAT START ==========")
+        LOG.debug("%s\n========== GENERIC FORMAT END ==========", entries)
+
+        nameservers = []
+        searchdomains = []
+        dev_names = entries.keys()
+        for (dev, info) in entries.iteritems():
+            # Skip the loopback interface.
+            if dev == 'lo0':
+                continue
+
+            LOG.info('Configuring interface %s', dev)
+
+            if info.get('bootproto') == 'static':
+                LOG.debug('Configuring dev %s with %s / %s', dev, info.get('address'), info.get('netmask'))
+                # Configure an ipv4 address.
+                ifconfig = info.get('address') + ' netmask ' + info.get('netmask')
+
+                # Configure the gateway.
+                self.updatercconf('defaultrouter', info.get('gateway'))
+
+                if 'dns-nameservers' in info:
+                    nameservers.extend(info['dns-nameservers'])
+                if 'dns-search' in info:
+                    searchservers.extend(info['dns-search'])
+            else:
+                ifconfig = 'DHCP'
+     
+            self.updatercconf('ifconfig_' + dev, ifconfig)
+
+        # Try to read the /etc/resolv.conf or just start from scratch if that
+        # fails.
+        try:
+            resolvconf = ResolvConf(util.load_file(self.resolv_conf_fn))
+            resolvconf.parse()
+        except IOError:
+            util.logexc(LOG, "Failed to parse %s, use new empty file", self.resolv_conf_fn)
+            resolvconf = ResolvConf('')
+            resolvconf.parse()
+
+        # Add some nameservers
+        for server in nameservers:
+            try:
+                resolvconf.add_nameserver(server)
+            except ValueError:
+                util.logexc(LOG, "Failed to add nameserver %s", server)
+
+        # And add any searchdomains.
+        for domain in searchdomains:
+            try:
+                resolvconf.add_search_domain(domain)
+            except ValueError:
+                util.logexc(LOG, "Failed to add search domain %s", domain)
+        util.write_file(self.resolv_conf_fn, str(resolvconf), 0644)
+
+        return dev_names
 
     def apply_locale(self, locale, out_fn=None):
         # Adjust the locals value to the new value

=== added file 'config/cloud.freebsd.cfg'
--- config/cloud.freebsd.cfg	1970-01-01 00:00:00 +0000
+++ config/cloud.freebsd.cfg	2014-08-15 20:32:20 +0000
@@ -0,0 +1,88 @@
+# 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: ['OpenStack']
+
+# 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: beastie
+     lock_passwd: True
+     gecos: FreeBSD
+     groups: [wheel]
+     sudo: ["ALL=(ALL) NOPASSWD:ALL"]
+     shell: /bin/sh

=== modified file 'setup.py'
--- setup.py	2014-07-24 13:06:16 +0000
+++ setup.py	2014-08-15 20:32:20 +0000
@@ -63,12 +63,14 @@
 
 INITSYS_FILES = {
     'sysvinit': [f for f in glob('sysvinit/redhat/*') if is_f(f)],
+    'sysvinit_freebsd': [f for f in glob('sysvinit/freebsd/*') if is_f(f)],
     'sysvinit_deb': [f for f in glob('sysvinit/debian/*') if is_f(f)],
     'systemd': [f for f in glob('systemd/*') if is_f(f)],
     'upstart': [f for f in glob('upstart/*') if is_f(f)],
 }
 INITSYS_ROOTS = {
     'sysvinit': '/etc/rc.d/init.d',
+    'sysvinit_freebsd': '/usr/local/etc/rc.d',
     'sysvinit_deb': '/etc/init.d',
     'systemd': systemd_unitdir(),
     'upstart': '/etc/init/',
@@ -88,6 +90,42 @@
     return str(deps).splitlines()
 
 
+# Install everything in the right location and take care of Linux (default) and
+# FreeBSD systems.
+def read_datafiles():
+    sysname = os.uname()[0]
+    if sysname == 'FreeBSD':
+        return [
+            ('/usr/local/etc/cloud', glob('config/*.cfg')),
+            ('/usr/local/etc/cloud/cloud.cfg.d', glob('config/cloud.cfg.d/*')),
+            ('/usr/local/etc/cloud/templates', glob('templates/*')),
+            ('/usr/local/share/cloud-init', []),
+            ('/usr/local/lib/cloud-init',
+                ['tools/uncloud-init', 'tools/write-ssh-key-fingerprints']),
+            ('/usr/local/share/doc/cloud-init',
+                [f for f in glob('doc/*') if is_f(f)]),
+            ('/usr/local/share/doc/cloud-init/examples',
+                [f for f in glob('doc/examples/*') if is_f(f)]),
+            ('/usr/local/share/doc/cloud-init/examples/seed',
+                [f for f in glob('doc/examples/seed/*') if is_f(f)]),
+        ]
+    else:
+        return [
+            ('/etc/cloud', glob('config/*.cfg')),
+            ('/etc/cloud/cloud.cfg.d', glob('config/cloud.cfg.d/*')),
+            ('/etc/cloud/templates', glob('templates/*')),
+            ('/usr/share/cloud-init', []),
+            ('/usr/lib/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)]),
+            ('/usr/share/doc/cloud-init/examples',
+                [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)]),
+        ]
+
+
 # TODO: Is there a better way to do this??
 class InitsysInstallData(install):
     init_system = None
@@ -136,20 +174,7 @@
                'tools/cloud-init-per',
                ],
       license='GPLv3',
-      data_files=[('/etc/cloud', glob('config/*.cfg')),
-                  ('/etc/cloud/cloud.cfg.d', glob('config/cloud.cfg.d/*')),
-                  ('/etc/cloud/templates', glob('templates/*')),
-                  ('/usr/share/cloud-init', []),
-                  ('/usr/lib/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)]),
-                  ('/usr/share/doc/cloud-init/examples',
-                   [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)]),
-                 ],
+      data_files=read_datafiles(),
       install_requires=read_requires(),
       cmdclass={
           # Use a subclass for install that handles

=== modified file 'sysvinit/freebsd/cloudconfig'
--- sysvinit/freebsd/cloudconfig	2014-01-18 00:45:23 +0000
+++ sysvinit/freebsd/cloudconfig	2014-08-15 20:32:20 +0000
@@ -6,28 +6,28 @@
 
 . /etc/rc.subr
 
+export CLOUD_CFG=/usr/local/etc/cloud/cloud.cfg
+
 name="cloudconfig"
-command="/usr/bin/cloud-init"
+command="/usr/local/bin/cloud-init"
 start_cmd="cloudconfig_start"
 stop_cmd=":"
 rcvar="cloudinit_enable"
 start_precmd="cloudinit_override"
 start_cmd="cloudconfig_start"
 
-: ${cloudinit_config:="/etc/cloud/cloud.cfg"}
-
 cloudinit_override()
 {
-	# If there exist sysconfig/default variable override files use it...
-	if [ -f /etc/default/cloud-init ]; then
-		. /etc/default/cloud-init
+	# If there exist sysconfig/defaults variable override files use it...
+	if [ -f /etc/defaults/cloud-init ]; then
+		. /etc/defaults/cloud-init
 	fi
 }
 
 cloudconfig_start()
 {
 	echo "${command} starting"
-	${command} ${cloudinit_config} modules --mode config
+	${command} modules --mode config
 }
 
 load_rc_config $name

=== modified file 'sysvinit/freebsd/cloudfinal'
--- sysvinit/freebsd/cloudfinal	2014-01-18 00:45:23 +0000
+++ sysvinit/freebsd/cloudfinal	2014-08-15 20:32:20 +0000
@@ -6,28 +6,28 @@
 
 . /etc/rc.subr
 
+export CLOUD_CFG=/usr/local/etc/cloud/cloud.cfg
+
 name="cloudfinal"
-command="/usr/bin/cloud_init"
+command="/usr/local/bin/cloud-init"
 start_cmd="cloudfinal_start"
 stop_cmd=":"
 rcvar="cloudinit_enable"
 start_precmd="cloudinit_override"
 start_cmd="cloudfinal_start"
 
-: ${cloudinit_config:="/etc/cloud/cloud.cfg"}
-
 cloudinit_override()
 {
-	# If there exist sysconfig/default variable override files use it...
-	if [ -f /etc/default/cloud-init ]; then
-		 . /etc/default/cloud-init
+	# If there exist sysconfig/defaults variable override files use it...
+	if [ -f /etc/defaults/cloud-init ]; then
+		 . /etc/defaults/cloud-init
 	fi
 }
 
 cloudfinal_start()
 {
 	echo -n "${command} starting"
-	${command} ${cloudinit_config} modules --mode final
+	${command} modules --mode final
 }
 
 load_rc_config $name

=== modified file 'sysvinit/freebsd/cloudinit'
--- sysvinit/freebsd/cloudinit	2014-01-18 00:45:23 +0000
+++ sysvinit/freebsd/cloudinit	2014-08-15 20:32:20 +0000
@@ -6,28 +6,28 @@
 
 . /etc/rc.subr
 
+export CLOUD_CFG=/usr/local/etc/cloud/cloud.cfg
+
 name="cloudinit"
-command="/usr/bin/cloud_init"
+command="/usr/local/bin/cloud-init"
 start_cmd="cloudinit_start"
 stop_cmd=":"
 rcvar="cloudinit_enable"
 start_precmd="cloudinit_override"
 start_cmd="cloudinit_start"
 
-: ${cloudinit_config:="/etc/cloud/cloud.cfg"}
-
 cloudinit_override()
 {
-	# If there exist sysconfig/default variable override files use it...
-	if [ -f /etc/default/cloud-init ]; then
-		. /etc/default/cloud-init
+	# If there exist sysconfig/defaults variable override files use it...
+	if [ -f /etc/defaults/cloud-init ]; then
+		. /etc/defaults/cloud-init
 	fi
 }
 
 cloudinit_start()
 {
 	echo -n "${command} starting"
-	${command} ${cloudinit_config} init
+	${command} init
 }
 
 load_rc_config $name

=== modified file 'sysvinit/freebsd/cloudinitlocal'
--- sysvinit/freebsd/cloudinitlocal	2014-01-18 00:45:23 +0000
+++ sysvinit/freebsd/cloudinitlocal	2014-08-15 20:32:20 +0000
@@ -6,28 +6,28 @@
 
 . /etc/rc.subr
 
+export CLOUD_CFG=/usr/local/etc/cloud/cloud.cfg
+
 name="cloudinitlocal"
-command="/usr/bin/cloud-init"
+command="/usr/local/bin/cloud-init"
 start_cmd="cloudlocal_start"
 stop_cmd=":"
 rcvar="cloudinit_enable"
 start_precmd="cloudinit_override"
 start_cmd="cloudlocal_start"
 
-: ${cloudinit_config:="/etc/cloud/cloud.cfg"}
-
 cloudinit_override()
 {
-	# If there exist sysconfig/default variable override files use it...
-	if [ -f /etc/default/cloud-init ]; then
-		. /etc/default/cloud-init
+	# If there exist sysconfig/defaults variable override files use it...
+	if [ -f /etc/defaults/cloud-init ]; then
+		. /etc/defaults/cloud-init
 	fi
 }
 
 cloudlocal_start()
 {
 	echo -n "${command} starting"
-	${command} ${cloudinit_config} init --local
+	${command} init --local
 }
 
 load_rc_config $name

=== modified file 'tests/unittests/test_distros/test_netconfig.py'
--- tests/unittests/test_distros/test_netconfig.py	2012-10-11 19:49:45 +0000
+++ tests/unittests/test_distros/test_netconfig.py	2014-08-15 20:32:20 +0000
@@ -173,3 +173,39 @@
 '''
         self.assertCfgEquals(expected_buf, str(write_buf))
         self.assertEquals(write_buf.mode, 0644)
+
+    def test_simple_write_freebsd(self):
+        fbsd_distro = self._get_distro('freebsd')
+        util_mock = self.mocker.replace(util.write_file,
+                                        spec=False, passthrough=False)
+        exists_mock = self.mocker.replace(os.path.isfile,
+                                          spec=False, passthrough=False)
+
+        exists_mock(mocker.ARGS)
+        self.mocker.count(0, None)
+        self.mocker.result(False)
+
+        write_bufs = {}
+
+        def replace_write(filename, content, mode=0644, omode="wb"):
+            buf = WriteBuffer()
+            buf.mode = mode
+            buf.omode = omode
+            buf.write(content)
+            write_bufs[filename] = buf
+
+        util_mock(mocker.ARGS)
+        self.mocker.call(replace_write)
+        self.mocker.replay()
+        fbsd_distro.apply_network(BASE_NET_CFG, False)
+
+        self.assertIn('/etc/rc.conf', write_bufs)
+        write_buf = write_bufs['/etc/rc.conf']
+        expected_buf = '''
+ifconfig_eth0="192.168.1.5 netmask 255.255.255.0"
+ifconfig_eth1="DHCP"
+defaultrouter="192.168.1.254"
+'''
+        self.assertCfgEquals(expected_buf, str(write_buf))
+        self.assertEquals(write_buf.mode, 0644)
+

=== added file 'tools/build-on-freebsd'
--- tools/build-on-freebsd	1970-01-01 00:00:00 +0000
+++ tools/build-on-freebsd	2014-08-15 20:32:20 +0000
@@ -0,0 +1,47 @@
+#!/bin/sh
+# Since there is no official FreeBSD port yet, we need some way of building and
+# installing cloud-init. This script takes care of building and installing. It
+# will optionally make a first run at the end.
+
+# Since there is no python by default, create a symlink for convenience sake:
+ln -sf /usr/local/bin/python2.7 /usr/local/bin/python
+
+# Check dependencies:
+[ ! -f /tmp/c-i.dependencieschecked ] && pkg install py27-cheetah py27-Jinja2 py27-prettytable py27-oauth py27-serial py27-configobj py27-yaml py27-argparse py27-requests py27-six py27-boto gpart sudo dmidecode
+touch /tmp/c-i.dependencieschecked
+
+# Required but unavailable port/pkg: py27-jsonpatch py27-jsonpointer
+# Luckily, the install step will take care of this by installing it from pypi...
+
+# Build the code and install in /usr/local/:
+python setup.py build
+python setup.py install -O1 --skip-build --prefix /usr/local/ --init-system sysvinit_freebsd
+
+# Use the correct config file:
+mv /usr/local/etc/cloud/cloud.freebsd.cfg /usr/local/etc/cloud/cloud.cfg
+
+# Enable cloud-init in /etc/rc.conf:
+sed -i.bak -e "/cloudinit_enable=.*/d" /etc/rc.conf
+echo 'cloudinit_enable="YES"' >> /etc/rc.conf
+
+echo "Installation completed."
+
+if [ "$1" = "run" ]
+then
+	echo "Ok, now let's see if it works."
+
+	# Backup SSH keys
+	mv /etc/ssh/ssh_host_* /tmp/
+
+	# Remove old metadata
+	rm -rf /var/lib/cloud
+
+	# Just log everything, quick&dirty
+	rm /usr/local/etc/cloud/cloud.cfg.d/05_logging.cfg 
+
+	# Start:
+	/usr/local/etc/rc.d/cloudinit start
+
+	# Restore SSH keys
+	mv /tmp/ssh_host_* /etc/ssh/
+fi


Follow ups