import os
import re
import shutil
+import datetime
import subprocess
import tempfile
import time
+from distro_info import DebianDistroInfo, UbuntuDistroInfo
-__version__ = '0.7'
+__version__ = '0.8'
-# pylint: disable=invalid-name
+# pylint: disable=invalid-name,line-too-long,missing-docstring,too-many-branches
class VmDebootstrap(cliapp.Application): # pylint: disable=too-many-public-methods
super(VmDebootstrap, self).__init__(progname, version, description, epilog)
self.remove_dirs = []
self.mount_points = []
+ self.debian_info = DebianDistroInfo()
+ self.ubuntu_info = UbuntuDistroInfo()
def add_settings(self):
default_arch = subprocess.check_output(
self.settings.boolean(
['no-kernel'],
'do not install a linux package')
+ self.settings.string(
+ ['kernel-package'],
+ 'install PACKAGE instead of the default kernel package',
+ metavar='PACKAGE')
self.settings.boolean(
['enable-dhcp'],
'enable DHCP on eth0')
self.settings.boolean(
['pkglist'],
'Create a list of package names included in the image.')
+ self.settings.boolean(
+ ['no-acpid'],
+ 'do not install the acpid package',
+ default=False)
def process_args(self, args): # pylint: disable=too-many-branches,too-many-statements
if not self.settings['image'] and not self.settings['tarball']:
if self.settings['image'] and not self.settings['size']:
raise cliapp.AppException(
'If disk image is specified, you must give image size.')
-
+ if not self.debian_info.valid(self.settings['distribution']):
+ if not self.ubuntu_info.valid(self.settings['distribution']):
+ raise cliapp.AppException(
+ '%s is not a valid Debian or Ubuntu suite or codename.'
+ % self.settings['distribution'])
rootdir = None
try:
rootdev = None
partoffset = self.settings['bootoffset'] / (1024 * 1024)
self.message("Using bootoffset: %smib %s bytes" % (partoffset, self.settings['bootoffset']))
if self.settings['bootsize'] and self.settings['bootsize'] is not '0%':
+ if self.settings['grub'] and not partoffset:
+ partoffset = 1
bootsize = self.settings['bootsize'] / (1024 * 1024)
bootsize += partoffset
self.message("Using bootsize %smib: %s bytes" % (bootsize, self.settings['bootsize']))
if line.startswith('add map ')]
if len(devices) != parts:
msg = 'Surprising number of partitions - check output of losetup -a'
- logging.debug("%s" % self.runcmd(['losetup', '-a']))
+ logging.debug("%s", self.runcmd(['losetup', '-a']))
logging.debug("%s: devices=%s parts=%s", msg, devices, parts)
raise cliapp.AppException(msg)
root = '/dev/mapper/%s' % devices[rootindex]
self.message('Creating filesystem %s' % fstype)
self.runcmd(['mkfs', '-t', fstype, device])
+ def suite_to_codename(self, distro):
+ suite = self.debian_info.codename(distro, datetime.date.today())
+ if not suite:
+ return distro
+ return suite
+
+ def was_oldstable(self, limit):
+ suite = self.suite_to_codename(self.settings['distribution'])
+ # this check is only for debian
+ if not self.debian_info.valid(suite):
+ return False
+ return suite == self.debian_info.old(limit)
+
+ def was_stable(self, limit):
+ suite = self.suite_to_codename(self.settings['distribution'])
+ # this check is only for debian
+ if not self.debian_info.valid(suite):
+ return False
+ return suite == self.debian_info.stable(limit)
+
def debootstrap(self, rootdir):
msg = "(%s)" % self.settings['variant'] if self.settings['variant'] else ''
self.message('Debootstrapping %s %s' % (self.settings['distribution'], msg))
include = self.settings['package']
- if not self.settings['foreign']:
+ if not self.settings['foreign'] and not self.settings['no-acpid']:
include.append('acpid')
if self.settings['grub']:
include.append('grub-pc')
if not self.settings['no-kernel']:
- if self.settings['arch'] == 'i386':
- kernel_arch = '486'
- elif self.settings['arch'] == 'armhf':
- kernel_arch = 'armmp'
+ if self.settings['kernel-package']:
+ kernel_image = self.settings['kernel-package']
else:
- kernel_arch = self.settings['arch']
- kernel_image = 'linux-image-%s' % kernel_arch
+ if self.settings['arch'] == 'i386':
+ # wheezy (which became oldstable on 04/25/2015) used '486'
+ if self.was_oldstable(datetime.date(2015, 4, 26)):
+ kernel_arch = '486'
+ else:
+ kernel_arch = '586'
+ elif self.settings['arch'] == 'armhf':
+ kernel_arch = 'armmp'
+ else:
+ kernel_arch = self.settings['arch']
+ kernel_image = 'linux-image-%s' % kernel_arch
include.append(kernel_image)
if self.settings['sudo'] and 'sudo' not in include:
def setup_networking(self, rootdir):
self.message('Setting up networking')
- f = open(os.path.join(rootdir, 'etc', 'network', 'interfaces'), 'w')
- f.write('auto lo\n')
- f.write('iface lo inet loopback\n')
+ if not os.path.exists(os.path.join(rootdir, 'etc', 'network', 'interfaces')):
+ with open(os.path.join(
+ rootdir, 'etc', 'network', 'interfaces'), 'w') as netfile:
+ netfile.write('source-directory /etc/network/interfaces.d\n')
- if self.settings['enable-dhcp']:
- f.write('\n')
- f.write('auto eth0\n')
- f.write('iface eth0 inet dhcp\n')
+ with open(os.path.join(
+ rootdir, 'etc', 'network', 'interfaces.d', 'setup'), 'w') as eth:
+ eth.write('auto lo\n')
+ eth.write('iface lo inet loopback\n')
- f.close()
+ if self.settings['enable-dhcp']:
+ eth.write('\n')
+ eth.write('auto eth0\n')
+ eth.write('iface eth0 inet dhcp\n')
def append_serial_console(self, rootdir):
if self.settings['serial-console']:
with open(inittab, 'a') as f:
f.write('\nS0:23:respawn:%s\n' % serial_command)
+ # pylint: disable=no-self-use
def _grub_serial_console(self, rootdir):
cmdline = 'GRUB_CMDLINE_LINUX_DEFAULT="console=tty0 console=tty1 console=ttyS0,38400n8"'
terminal = 'GRUB_TERMINAL="serial gfxterm"'
self.runcmd(['chroot', rootdir, 'grub-install', install_dev])
except cliapp.AppException:
self.message("Failed. Is grub2-common installed? Using extlinux.")
+ self.install_extlinux(rootdev, rootdir)
self.runcmd(['umount', os.path.join(rootdir, 'sys')])
self.runcmd(['umount', os.path.join(rootdir, 'proc')])
self.runcmd(['umount', os.path.join(rootdir, 'dev')])
- self.install_extlinux(rootdev, rootdir)
def install_extlinux(self, rootdev, rootdir):
if not os.path.exists("/usr/bin/extlinux"):
with open('/dev/tty', 'w') as tty:
try:
cliapp.runcmd([script, rootdir, self.settings['image']], stdout=tty, stderr=tty)
- except IOError as e:
+ except IOError:
subprocess.call([script, rootdir, self.settings['image']])
def create_tarball(self, rootdir):