bootstrap-vz/bootstrapvz/common/tasks/packages.py
2018-02-25 10:27:53 +00:00

110 lines
4.3 KiB
Python

from bootstrapvz.base import Task
from .. import phases
from . import apt
from ..tools import log_check_call
class AddManifestPackages(Task):
description = 'Adding packages from the manifest'
phase = phases.preparation
predecessors = [apt.AddManifestSources, apt.AddDefaultSources, apt.AddBackports]
@classmethod
def run(cls, info):
import re
remote = re.compile('^(?P<name>[^/]+)(/(?P<target>[^/]+))?$')
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 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
@classmethod
def install_local(cls, info, local_packages):
from shutil import copy
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):
tasksel_packages = log_check_call(['chroot', info.root, 'tasksel', '--task-packages', 'standard'])
for pkg in tasksel_packages:
info.packages.add(pkg)