summaryrefslogtreecommitdiff
path: root/morphlib/exts
diff options
context:
space:
mode:
Diffstat (limited to 'morphlib/exts')
-rwxr-xr-xmorphlib/exts/fstab.configure25
-rwxr-xr-xmorphlib/exts/hosts.configure48
-rwxr-xr-xmorphlib/exts/install-files.configure42
-rwxr-xr-xmorphlib/exts/kvm.check23
-rwxr-xr-xmorphlib/exts/simple-network.configure39
5 files changed, 136 insertions, 41 deletions
diff --git a/morphlib/exts/fstab.configure b/morphlib/exts/fstab.configure
index 3bbc9102..b9154eee 100755
--- a/morphlib/exts/fstab.configure
+++ b/morphlib/exts/fstab.configure
@@ -1,5 +1,6 @@
-#!/usr/bin/python
-# Copyright (C) 2013,2015 Codethink Limited
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# Copyright © 2013-2015 Codethink Limited
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
@@ -19,21 +20,9 @@
import os
import sys
+import morphlib
-def asciibetical(strings):
+envvars = {k: v for (k, v) in os.environ.iteritems() if k.startswith('FSTAB_')}
- def key(s):
- return [ord(c) for c in s]
-
- return sorted(strings, key=key)
-
-
-fstab_filename = os.path.join(sys.argv[1], 'etc', 'fstab')
-
-fstab_vars = asciibetical(x for x in os.environ if x.startswith('FSTAB_'))
-with open(fstab_filename, 'a') as f:
- for var in fstab_vars:
- f.write('%s\n' % os.environ[var])
-
-os.chown(fstab_filename, 0, 0)
-os.chmod(fstab_filename, 0644)
+conf_file = os.path.join(sys.argv[1], 'etc/fstab')
+morphlib.util.write_from_dict(conf_file, envvars)
diff --git a/morphlib/exts/hosts.configure b/morphlib/exts/hosts.configure
new file mode 100755
index 00000000..6b068d04
--- /dev/null
+++ b/morphlib/exts/hosts.configure
@@ -0,0 +1,48 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# Copyright © 2015 Codethink Limited
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+#
+# =*= License: GPL-2 =*=
+
+
+import os
+import sys
+import socket
+
+import morphlib
+
+def validate(var, line):
+ xs = line.split()
+ if len(xs) == 0:
+ raise morphlib.Error("`%s: %s': line is empty" % (var, line))
+
+ ip = xs[0]
+ hostnames = xs[1:]
+
+ if len(hostnames) == 0:
+ raise morphlib.Error("`%s: %s': missing hostname" % (var, line))
+
+ family = socket.AF_INET6 if ':' in ip else socket.AF_INET
+
+ try:
+ socket.inet_pton(family, ip)
+ except socket.error:
+ raise morphlib.Error("`%s: %s' invalid ip" % (var, ip))
+
+envvars = {k: v for (k, v) in os.environ.iteritems() if k.startswith('HOSTS_')}
+
+conf_file = os.path.join(sys.argv[1], 'etc/hosts')
+morphlib.util.write_from_dict(conf_file, envvars, validate)
diff --git a/morphlib/exts/install-files.configure b/morphlib/exts/install-files.configure
index 58cf373a..c2970243 100755
--- a/morphlib/exts/install-files.configure
+++ b/morphlib/exts/install-files.configure
@@ -30,6 +30,12 @@ import shlex
import shutil
import stat
+try:
+ import jinja2
+ jinja_available = True
+except ImportError:
+ jinja_available = False
+
class InstallFilesConfigureExtension(cliapp.Application):
def process_args(self, args):
@@ -48,18 +54,20 @@ class InstallFilesConfigureExtension(cliapp.Application):
self.install_entry(entry, manifest_dir, target_root)
def install_entry(self, entry, manifest_root, target_root):
- m = re.match('(overwrite )?([0-7]+) ([0-9]+) ([0-9]+) (\S+)', entry)
+ m = re.match('(template )?(overwrite )?'
+ '([0-7]+) ([0-9]+) ([0-9]+) (\S+)', entry)
if m:
- overwrite = m.group(1)
- mode = int(m.group(2), 8) # mode is octal
- uid = int(m.group(3))
- gid = int(m.group(4))
- path = m.group(5)
+ template = m.group(1)
+ overwrite = m.group(2)
+ mode = int(m.group(3), 8) # mode is octal
+ uid = int(m.group(4))
+ gid = int(m.group(5))
+ path = m.group(6)
else:
raise cliapp.AppException('Invalid manifest entry, '
- 'format: [overwrite] <octal mode> <uid decimal> <gid decimal> '
- '<filename>')
+ 'format: [template] [overwrite] '
+ '<octal mode> <uid decimal> <gid decimal> <filename>')
dest_path = os.path.join(target_root, './' + path)
if stat.S_ISDIR(mode):
@@ -91,8 +99,22 @@ class InstallFilesConfigureExtension(cliapp.Application):
raise cliapp.AppException('File already exists at %s'
% dest_path)
else:
- shutil.copyfile(os.path.join(manifest_root, './' + path),
- dest_path)
+ if template:
+ if not jinja_available:
+ raise cliapp.AppException(
+ "Failed to install template file `%s': "
+ 'install-files templates require jinja2'
+ % path)
+
+ loader = jinja2.FileSystemLoader(manifest_root)
+ env = jinja2.Environment(loader=loader,
+ keep_trailing_newline=True)
+
+ env.get_template(path).stream(os.environ).dump(dest_path)
+ else:
+ shutil.copyfile(os.path.join(manifest_root, './' + path),
+ dest_path)
+
os.chown(dest_path, uid, gid)
os.chmod(dest_path, mode)
diff --git a/morphlib/exts/kvm.check b/morphlib/exts/kvm.check
index 62d76453..67cb3d38 100755
--- a/morphlib/exts/kvm.check
+++ b/morphlib/exts/kvm.check
@@ -47,6 +47,7 @@ class KvmPlusSshCheckExtension(morphlib.writeexts.WriteExtension):
self.check_no_existing_libvirt_vm(ssh_host, vm_name)
self.check_extra_disks_exist(ssh_host, self.parse_attach_disks())
self.check_virtual_networks_are_started(ssh_host)
+ self.check_host_has_virtinstall(ssh_host)
def check_and_parse_location(self, location):
'''Check and parse the location argument to get relevant data.'''
@@ -129,14 +130,22 @@ class KvmPlusSshCheckExtension(morphlib.writeexts.WriteExtension):
def name(nic_entry):
if ',' in nic_entry:
- # NETWORK_NAME,mac=12:34,model=e1000...
- return nic_entry[:nic_entry.find(',')]
+ # network=NETWORK_NAME,mac=12:34,model=e1000...
+ return nic_entry[:nic_entry.find(',')].lstrip('network=')
else:
- return nic_entry # NETWORK_NAME
+ return nic_entry.lstrip('network=') # NETWORK_NAME
if 'NIC_CONFIG' in os.environ:
nics = os.environ['NIC_CONFIG'].split()
+ for n in nics:
+ if not (n.startswith('network=')
+ or n.startswith('bridge=')
+ or n == 'user'):
+ raise cliapp.AppException('malformed NIC_CONFIG: %s\n'
+ " (expected 'bridge=BRIDGE' 'network=NAME'"
+ " or 'user')" % n)
+
# --network bridge= is used to specify a bridge
# --network user is used to specify a form of NAT
# (see the virt-install(1) man page)
@@ -148,5 +157,13 @@ class KvmPlusSshCheckExtension(morphlib.writeexts.WriteExtension):
for network in networks:
check_virtual_network_is_started(network)
+ def check_host_has_virtinstall(self, ssh_host):
+ try:
+ cliapp.ssh_runcmd(ssh_host, ['which', 'virt-install'])
+ except cliapp.AppException:
+ raise cliapp.AppException(
+ 'virt-install does not seem to be installed on host %s'
+ % ssh_host)
+
KvmPlusSshCheckExtension().run()
diff --git a/morphlib/exts/simple-network.configure b/morphlib/exts/simple-network.configure
index 61113325..1ba94e86 100755
--- a/morphlib/exts/simple-network.configure
+++ b/morphlib/exts/simple-network.configure
@@ -27,6 +27,7 @@ for DHCP
import os
import sys
+import errno
import cliapp
import morphlib
@@ -80,12 +81,14 @@ class SimpleNetworkConfigurationExtension(cliapp.Application):
"""
file_path = os.path.join(args[0], "etc", "systemd", "network",
"10-dhcp.network")
- try:
- os.rename(file_path, file_path + ".morph")
- self.status(msg="Renaming networkd file from systemd chunk: %(f)s \
- to %(f)s.morph", f=file_path)
- except OSError:
- pass
+
+ if os.path.isfile(file_path):
+ try:
+ os.rename(file_path, file_path + ".morph")
+ self.status(msg="Renaming networkd file from systemd chunk: \
+ %(f)s to %(f)s.morph", f=file_path)
+ except OSError:
+ pass
def generate_default_network_config(self, args):
"""Generate default network config: DHCP in all the interfaces"""
@@ -106,7 +109,11 @@ class SimpleNetworkConfigurationExtension(cliapp.Application):
"""Generate /etc/network/interfaces file"""
iface_file = self.generate_iface_file(stanzas)
- with open(os.path.join(args[0], "etc/network/interfaces"), "w") as f:
+
+ directory_path = os.path.join(args[0], "etc", "network")
+ self.make_sure_path_exists(directory_path)
+ file_path = os.path.join(directory_path, "interfaces")
+ with open(file_path, "w") as f:
f.write(iface_file)
def generate_iface_file(self, stanzas):
@@ -147,10 +154,12 @@ class SimpleNetworkConfigurationExtension(cliapp.Application):
if iface_file is None:
continue
- path = os.path.join(args[0], "etc", "systemd", "network",
- "%s-%s.network" % (i, stanza['name']))
+ directory_path = os.path.join(args[0], "etc", "systemd", "network")
+ self.make_sure_path_exists(directory_path)
+ file_path = os.path.join(directory_path,
+ "%s-%s.network" % (i, stanza['name']))
- with open(path, "w") as f:
+ with open(file_path, "w") as f:
f.write(iface_file)
def generate_networkd_file(self, stanza):
@@ -252,6 +261,16 @@ class SimpleNetworkConfigurationExtension(cliapp.Application):
return output_stanza
+ def make_sure_path_exists(self, path):
+ try:
+ os.makedirs(path)
+ except OSError as e:
+ if e.errno == errno.EEXIST and os.path.isdir(path):
+ pass
+ else:
+ raise SimpleNetworkError("Unable to create directory '%s'"
+ % path)
+
def status(self, **kwargs):
'''Provide status output.