kiwi-el8/kiwi/package_manager/apt.py
Marcus Schäfer dd1052ac99
Handle location of the rpm DB on the macro level
The location of the rpm database is no longer a standard
path one can trust. Some distributions put it to /var/lib
others to /usr/lib. This introduces the problem of dealing
with different locations between the bootstrapping (host rpm)
phase and the image installation (image rpm) phase.

This commit implements a solution based on an intermediate
rpm database configuration. KIWI creates the macros.kiwi file
inside of the image root which is read by any call of rpm
in the inner and outer system. During bootstrap phase the
rpm dbpath from the host system is used and later in the
install phase the dbpath from the rpm package as it was
installed by the target image distribution is used. In case
of a dbpath difference the database is automatically moved
to the new location by setting the _dbpath_rebuild macro
to the correct location. At the end the custom KIWI macro
is deleted.

As this process allows custom macro defintions during the
KIWI run it also serves as the base for a solution to
Issue #771 which will be done in a follow up request to
this commit.

Also the workaround for bsc#1112357 which uses a static
dbpath to store an optionally given signing key will be
addressed with this commit. The macro setup happens before
the import_trusted_keys method which makes any specification
for a strict dbpath obsolete.

Last the implementation deletes the obsolete
dump_reload_package_database code. rpm is able to automatically
do the conversion of different db versions such that the code
in kiwi is obsolete. In addition that code only worked for
rather old db versions. The public API has not changed though,
but the method is marked obsolete and does nothing anymore.

In addition to the deletion of obsolete code a new API
method post_process_install_requests_bootstrap has been
introduced to handle actions required after bootstrap
and before installing of packages from inside the new image
2019-02-06 11:55:42 +01:00

313 lines
10 KiB
Python

# Copyright (c) 2015 SUSE Linux GmbH. All rights reserved.
#
# This file is part of kiwi.
#
# kiwi 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, either version 3 of the License, or
# (at your option) any later version.
#
# kiwi 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 kiwi. If not, see <http://www.gnu.org/licenses/>
#
import re
import os
# project
from kiwi.command import Command
from kiwi.mount_manager import MountManager
from kiwi.logger import log
from kiwi.utils.sync import DataSync
from kiwi.path import Path
from kiwi.package_manager.base import PackageManagerBase
from kiwi.exceptions import (
KiwiDebootstrapError,
KiwiRequestError
)
class PackageManagerApt(PackageManagerBase):
"""
**Implements base class for installation/deletion of
packages and collections using apt-get**
:param list apt_get_args: apt-get arguments from repository runtime
configuration
:param dict command_env: apt-get command environment from repository
runtime configuration
"""
def post_init(self, custom_args=None):
"""
Post initialization method
Store custom apt-get arguments
:param list custom_args: custom apt-get arguments
"""
self.custom_args = custom_args
if not custom_args:
self.custom_args = []
self.deboostrap_minbase = True
runtime_config = self.repository.runtime_config()
self.apt_get_args = runtime_config['apt_get_args']
self.command_env = runtime_config['command_env']
self.distribution = runtime_config['distribution']
self.distribution_path = runtime_config['distribution_path']
def request_package(self, name):
"""
Queue a package request
:param str name: package name
"""
self.package_requests.append(name)
def request_collection(self, name):
"""
Queue a collection request
There is no collection definition in the deb repo data
:param str name: unused
"""
log.warning(
'Collection(%s) handling not supported for apt-get', name
)
def request_product(self, name):
"""
Queue a product request
There is no product definition in the deb repo data
:param str name: unused
"""
log.warning(
'Product(%s) handling not supported for apt-get', name
)
def request_package_exclusion(self, name):
"""
Queue a package exclusion(skip) request
Package exclusion for apt package manager not yet implemented
:param str name: unused
"""
log.warning(
'Package exclusion for (%s) not supported for apt-get', name
)
def process_install_requests_bootstrap(self):
"""
Process package install requests for bootstrap phase (no chroot)
The debootstrap program is used to bootstrap a new system with
a collection of predefined packages. The kiwi bootstrap section
information is not used in this case
:raises KiwiDebootstrapError: if no main distribution repository
is configured, if the debootstrap script is not found or if the
debootstrap script execution fails
:return: process results in command type
:rtype: namedtuple
"""
if not self.distribution:
raise KiwiDebootstrapError(
'No main distribution repository is configured'
)
bootstrap_script = '/usr/share/debootstrap/scripts/' + \
self.distribution
if not os.path.exists(bootstrap_script):
raise KiwiDebootstrapError(
'debootstrap script for %s distribution not found' %
self.distribution
)
bootstrap_dir = self.root_dir + '.debootstrap'
if 'apt-get' in self.package_requests:
# debootstrap takes care to install apt-get
self.package_requests.remove('apt-get')
try:
dev_mount = MountManager(
device='/dev', mountpoint=self.root_dir + '/dev'
)
dev_mount.umount()
if self.repository.unauthenticated == 'false':
log.warning(
'KIWI does not support signature checks for apt-get '
'package manager during the bootstrap procedure, any '
'provided key will only be used inside the chroot '
'environment'
)
cmd = ['debootstrap', '--no-check-gpg']
if self.deboostrap_minbase:
cmd.append('--variant=minbase')
cmd.extend([
self.distribution, bootstrap_dir, self.distribution_path
])
Command.run(cmd, self.command_env)
data = DataSync(
bootstrap_dir + '/', self.root_dir
)
data.sync_data(
options=['-a', '-H', '-X', '-A']
)
for key in self.repository.signing_keys:
Command.run([
'chroot', self.root_dir, 'apt-key', 'add', key
], self.command_env)
except Exception as e:
raise KiwiDebootstrapError(
'%s: %s' % (type(e).__name__, format(e))
)
finally:
Path.wipe(bootstrap_dir)
return self.process_install_requests()
def process_install_requests(self):
"""
Process package install requests for image phase (chroot)
:return: process results in command type
:rtype: namedtuple
"""
update_command = ['chroot', self.root_dir, 'apt-get']
update_command.extend(self.root_bind.move_to_root(self.apt_get_args))
update_command.extend(self.custom_args)
update_command.append('update')
Command.run(update_command, self.command_env)
apt_get_command = ['chroot', self.root_dir, 'apt-get']
apt_get_command.extend(self.root_bind.move_to_root(self.apt_get_args))
apt_get_command.extend(self.custom_args)
apt_get_command.append('install')
apt_get_command.extend(self._package_requests())
return Command.call(
apt_get_command, self.command_env
)
def process_delete_requests(self, force=False):
"""
Process package delete requests (chroot)
:param bool force: force deletion: true|false
:raises KiwiRequestError: if none of the packages to delete is
installed
:return: process results in command type
:rtype: namedtuple
"""
delete_items = []
for delete_item in self._package_requests():
try:
Command.run(
['chroot', self.root_dir, 'dpkg', '-l', delete_item]
)
delete_items.append(delete_item)
except Exception:
# ignore packages which are not installed
pass
if not delete_items:
raise KiwiRequestError(
'None of the requested packages to delete are installed'
)
if force:
apt_get_command = ['chroot', self.root_dir, 'dpkg']
apt_get_command.extend(['--force-all', '-r'])
apt_get_command.extend(delete_items)
else:
apt_get_command = ['chroot', self.root_dir, 'apt-get']
apt_get_command.extend(
self.root_bind.move_to_root(self.apt_get_args)
)
apt_get_command.extend(self.custom_args)
apt_get_command.extend(['--auto-remove', 'remove'])
apt_get_command.extend(delete_items)
return Command.call(
apt_get_command, self.command_env
)
def update(self):
"""
Process package update requests (chroot)
:return: process results in command type
:rtype: namedtuple
"""
apt_get_command = ['chroot', self.root_dir, 'apt-get']
apt_get_command.extend(self.root_bind.move_to_root(self.apt_get_args))
apt_get_command.extend(self.custom_args)
apt_get_command.append('upgrade')
return Command.call(
apt_get_command, self.command_env
)
def process_only_required(self):
"""
Setup package processing only for required packages
"""
if '--no-install-recommends' not in self.custom_args:
self.custom_args.append('--no-install-recommends')
self.deboostrap_minbase = True
def process_plus_recommended(self):
"""
Setup package processing to also include recommended dependencies.
"""
if '--no-install-recommends' in self.custom_args:
self.custom_args.remove('--no-install-recommends')
self.deboostrap_minbase = False
def match_package_installed(self, package_name, apt_get_output):
"""
Match expression to indicate a package has been installed
This match for the package to be installed in the output
of the apt-get command is not 100% accurate. There might
be false positives due to sub package names starting with
the same base package name
:param list package_list: list of all packages
:param str log_line: apt-get status line
:returns: match or None if there isn't any match
:rtype: match object, None
"""
return re.match(
'.*Unpacking ' + re.escape(package_name) + '.*', apt_get_output
)
def match_package_deleted(self, package_name, apt_get_output):
"""
Match expression to indicate a package has been deleted
:param list package_list: list of all packages
:param str log_line: apt-get status line
:returns: match or None if there isn't any match
:rtype: match object, None
"""
return re.match(
'.*Removing ' + re.escape(package_name) + '.*', apt_get_output
)
def _package_requests(self):
items = self.package_requests[:]
self.cleanup_requests()
return items