from base import Task from common import phases from common.tasks import apt class AddManifestPackages(Task): description = 'Adding packages from the manifest' phase = phases.preparation predecessors = [apt.AddDefaultSources] @classmethod def run(cls, info): import re remote = re.compile('^(?P[^/]+)(/(?P[^/]+))?$') for package in info.manifest.packages['install']: match = remote.match(package) if match is not None: info.packages.add(match.group('name'), match.group('target')) else: info.packages.add_local(package) class InstallPackages(Task): description = 'Installing packages' phase = phases.package_installation predecessors = [apt.AptUpgrade] @classmethod def run(cls, info): batch = [] actions = {info.packages.Remote: cls.install_remote, info.packages.Local: cls.install_local} for i, package in enumerate(info.packages.install): batch.append(package) next_package = info.packages.install[i + 1] if i + 1 < len(info.packages.install) else None if next_package is None or package.__class__ is not next_package.__class__: actions[package.__class__](info, batch) batch = [] @classmethod def install_remote(cls, info, remote_packages): import os from common.tools import log_check_call from subprocess import CalledProcessError try: env = os.environ.copy() env['DEBIAN_FRONTEND'] = 'noninteractive' log_check_call(['chroot', info.root, 'apt-get', 'install', '--no-install-recommends', '--assume-yes'] + map(str, remote_packages), env=env) except CalledProcessError as e: import logging disk_stat = os.statvfs(info.root) root_free_mb = disk_stat.f_bsize * disk_stat.f_bavail / 1024 / 1024 disk_stat = os.statvfs(os.path.join(info.root, 'boot')) boot_free_mb = disk_stat.f_bsize * disk_stat.f_bavail / 1024 / 1024 free_mb = min(root_free_mb, boot_free_mb) if free_mb < 50: msg = ('apt exited with a non-zero status, ' 'this may be because\nthe image volume is ' 'running out of disk space ({free}MB left)').format(free=free_mb) logging.getLogger(__name__).warn(msg) else: if e.returncode == 100: msg = ('apt exited with status code 100. ' 'This can sometimes occur when package retrieval times out or a package extraction failed. ' 'apt might succeed if you try bootstrapping again.') logging.getLogger(__name__).warn(msg) raise e @classmethod def install_local(cls, info, local_packages): from shutil import copy from common.tools import log_check_call import os absolute_package_paths = [] chrooted_package_paths = [] for package_src in local_packages: pkg_name = os.path.basename(package_src.path) package_rel_dst = os.path.join('tmp', pkg_name) package_dst = os.path.join(info.root, package_rel_dst) copy(package_src.path, package_dst) absolute_package_paths.append(package_dst) package_path = os.path.join('/', package_rel_dst) chrooted_package_paths.append(package_path) env = os.environ.copy() env['DEBIAN_FRONTEND'] = 'noninteractive' log_check_call(['chroot', info.root, 'dpkg', '--install'] + chrooted_package_paths, env=env) for path in absolute_package_paths: os.remove(path) class AddTaskselStandardPackages(Task): description = 'Adding standard packages from tasksel' phase = phases.package_installation predecessors = [apt.AptUpdate] successors = [InstallPackages] @classmethod def run(cls, info): import subprocess tasksel_packages = subprocess.check_output(['chroot', info.root, 'tasksel', '--task-packages', 'standard']).strip().split('\n') for pkg in tasksel_packages: info.packages.add(pkg)