diff --git a/Makefile b/Makefile index 823c1e6c..ef224bad 100644 --- a/Makefile +++ b/Makefile @@ -132,6 +132,10 @@ build: clean tox # update package version in spec file cat package/python-kiwi-spec-template | sed -e s'@%%VERSION@${version}@' \ > dist/python-kiwi.spec + # update package version in PKGBUILD file + md5sums=$$(md5sum dist/python-kiwi.tar.gz | cut -d" " -f1); \ + cat package/python-kiwi-pkgbuild-template | sed -e s'@%%VERSION@${version}@' \ + -e s"@%%MD5SUM@$${md5sums}@" > dist/PKGBUILD # provide rpm rpmlintrc cp package/python-kiwi-rpmlintrc dist diff --git a/kiwi/defaults.py b/kiwi/defaults.py index d597e3bc..616a5b87 100644 --- a/kiwi/defaults.py +++ b/kiwi/defaults.py @@ -672,6 +672,7 @@ class Defaults: """ return [ '/usr/share/syslinux', + '/usr/lib/syslinux/bios', '/usr/lib/syslinux/modules/bios', '/usr/lib/ISOLINUX' ] @@ -1470,6 +1471,8 @@ class Defaults: return 'rpm' elif package_manager in deb_based: return 'dpkg' + elif package_manager == 'pacman': + return 'pacman' def get(self, key): """ diff --git a/kiwi/package_manager/__init__.py b/kiwi/package_manager/__init__.py index 2a1576de..4604ff88 100644 --- a/kiwi/package_manager/__init__.py +++ b/kiwi/package_manager/__init__.py @@ -21,6 +21,7 @@ import logging from kiwi.package_manager.zypper import PackageManagerZypper from kiwi.package_manager.apt import PackageManagerApt from kiwi.package_manager.dnf import PackageManagerDnf +from kiwi.package_manager.pacman import PackageManagerPacman from kiwi.exceptions import ( KiwiPackageManagerSetupError @@ -50,6 +51,8 @@ class PackageManager: manager = PackageManagerDnf(repository, custom_args) elif package_manager == 'apt-get': manager = PackageManagerApt(repository, custom_args) + elif package_manager == 'pacman': + manager = PackageManagerPacman(repository, custom_args) else: raise KiwiPackageManagerSetupError( 'Support for package manager %s not implemented' % diff --git a/kiwi/package_manager/pacman.py b/kiwi/package_manager/pacman.py new file mode 100644 index 00000000..3fc82a2f --- /dev/null +++ b/kiwi/package_manager/pacman.py @@ -0,0 +1,244 @@ +# Copyright (c) 2019 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 +# +import re +import logging + +# project +from kiwi.command import Command +from kiwi.package_manager.base import PackageManagerBase +from kiwi.exceptions import KiwiRequestError +from kiwi.path import Path + +log = logging.getLogger('kiwi') + + +class PackageManagerPacman(PackageManagerBase): + """ + **Implements base class for installation/deletion of + packages and collections using pacman** + + :param list pacman_args: pacman arguments from repository runtime + configuration + :param dict command_env: pacman command environment from repository + runtime configuration + """ + def post_init(self, custom_args=None): + """ + Post initialization method + + Store custom pacman arguments + + :param list custom_args: custom pacman arguments + """ + self.custom_args = custom_args + if not custom_args: + self.custom_args = [] + + runtime_config = self.repository.runtime_config() + self.pacman_args = runtime_config['pacman_args'] + self.command_env = runtime_config['command_env'] + + 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 + + Pacman does not distinguish the installation of pacakges + and collections. Because of that collections are listed together + with package requests and a warning message is shown. + :param str name: pacman group name + """ + log.warning(( + 'Pacman treats collection installations as regular packages.' + 'It is preferred to list them as regular packages' + )) + self.package_requests.append(name) + + def request_product(self, name): + """ + Queue a product request + + There is no product definition in the Arch Linux repo data + + :param str name: unused + """ + pass + + def request_package_exclusion(self, name): + """ + Queue a package exclusion(skip) request + + :param str name: package name + """ + self.exclude_requests.append(name) + + def process_install_requests_bootstrap(self): + """ + Process package install requests for bootstrap phase (no chroot) + + :return: process results in command type + + :rtype: namedtuple + """ + Command.run( + ['pacman'] + self.pacman_args + [ + '--root', self.root_dir, '-Sy' + ] + ) + bash_command = [ + 'pacman' + ] + self.pacman_args + [ + '--root', self.root_dir + ] + self.custom_args + [ + '-S', '--needed', + '--overwrite', '{}/var/run'.format(self.root_dir) + ] + self.package_requests + self.cleanup_requests() + return Command.call( + ['bash', '-c', ' '.join(bash_command)], self.command_env + ) + + def process_install_requests(self): + """ + Process package install requests for image phase (chroot) + + :return: process results in command type + + :rtype: namedtuple + """ + chroot_pacman_args = Path.move_to_root(self.root_dir, self.pacman_args) + bash_command = [ + 'chroot', self.root_dir, 'pacman' + ] + chroot_pacman_args + self.custom_args + [ + '-S', '--needed' + ] + self.package_requests + self.cleanup_requests() + return Command.call( + ['bash', '-c', ' '.join(bash_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, 'pacman', '-Qi', 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' + ) + self.cleanup_requests() + chroot_pacman_args = Path.move_to_root(self.root_dir, self.pacman_args) + return Command.call( + [ + 'chroot', self.root_dir, 'pacman' + ] + chroot_pacman_args + self.custom_args + [ + '-Rdd' if force else '-Rs' + ] + delete_items, + self.command_env + ) + + def update(self): + """ + Process package update requests (chroot) + + :return: process results in command type + + :rtype: namedtuple + """ + chroot_pacman_args = Path.move_to_root(self.root_dir, self.pacman_args) + return Command.call( + [ + 'chroot', self.root_dir, 'pacman' + ] + chroot_pacman_args + self.custom_args + [ + '-Su' + ], + self.command_env + ) + + def process_only_required(self): + """ + Setup package processing only for required packages. + + This is the only option in pacman, nothing to do here. + """ + pass + + def process_plus_recommended(self): + """ + Setup package processing to also include recommended dependencies. + + There is no such concept of recommended dependencies in pacman. + """ + pass + + def match_package_installed(self, package_name, pacman_output): + """ + Match expression to indicate a package has been installed + + This match for the package to be installed in the output + of the pacman 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: dnf status line + + :returns: match or None if there isn't any match + + :rtype: match object, None + """ + return re.match( + '.* installing ' + re.escape(package_name) + '.*', pacman_output + ) + + def match_package_deleted(self, package_name, pacman_output): + """ + Match expression to indicate a package has been deleted + + :param list package_list: list of all packages + :param str log_line: pacman status line + + :returns: match or None if there isn't any match + + :rtype: match object, None + """ + return re.match( + '.* removing ' + re.escape(package_name) + '.*', pacman_output + ) diff --git a/kiwi/repository/__init__.py b/kiwi/repository/__init__.py index decf9493..30b41ddc 100644 --- a/kiwi/repository/__init__.py +++ b/kiwi/repository/__init__.py @@ -19,6 +19,7 @@ from kiwi.repository.zypper import RepositoryZypper from kiwi.repository.apt import RepositoryApt from kiwi.repository.dnf import RepositoryDnf +from kiwi.repository.pacman import RepositoryPacman from kiwi.exceptions import ( KiwiRepositorySetupError @@ -43,6 +44,8 @@ class Repository: return RepositoryDnf(root_bind, custom_args) elif package_manager == 'apt-get': return RepositoryApt(root_bind, custom_args) + elif package_manager == 'pacman': + return RepositoryPacman(root_bind, custom_args) else: raise KiwiRepositorySetupError( 'Support for %s repository manager not implemented' % diff --git a/kiwi/repository/pacman.py b/kiwi/repository/pacman.py new file mode 100644 index 00000000..0cfe3d3d --- /dev/null +++ b/kiwi/repository/pacman.py @@ -0,0 +1,247 @@ +# Copyright (c) 2019 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 +# +import os +from configparser import ConfigParser +from tempfile import NamedTemporaryFile + +# project +from kiwi.repository.base import RepositoryBase +from kiwi.path import Path +from kiwi.command import Command + + +class RepositoryPacman(RepositoryBase): + """ + **Implements repo handling for pacman package manager** + + :param str shared_pacman_dir: shared directory between image root + and build system root + :param str runtime_pacman_config_file: pacman runtime config file name + :param list pacman_args: zypper caller args plus additional custom args + :param dict command_env: customized os.environ for zypper + """ + def post_init(self, custom_args=None): + """ + Post initialization method + + Store custom pacman arguments and create runtime configuration + and environment + + :param list custom_args: pacman arguments + """ + self.custom_args = custom_args + self.check_signatures = False + self.repo_names = [] + if not custom_args: + self.custom_args = [] + + self.runtime_pacman_config_file = NamedTemporaryFile( + dir=self.root_dir + ) + + if 'check_signatures' in self.custom_args: + self.custom_args.remove('check_signatures') + self.check_signatures = True + + manager_base = self.shared_location + '/pacman' + + self.shared_pacman_dir = { + 'cache-dir': manager_base + '/cache', + 'repos-dir': manager_base + '/repos' + } + Path.create(self.shared_pacman_dir['repos-dir']) + + self.pacman_args = [ + '--config', self.runtime_pacman_config_file.name, + '--noconfirm' + ] + + self._write_runtime_config() + + def use_default_location(self): + """ + Setup pacman repository operations to store all data + in the default places + """ + self.shared_pacman_dir['repos-dir'] = \ + self.root_dir + '/etc/pacman.d' + + def runtime_config(self): + """ + pacman runtime configuration and environment + """ + return { + 'pacman_args': self.pacman_args, + 'command_env': os.environ + } + + def _add_repo_section( + self, config_parser, name, uri, repo_gpgcheck=None, pkg_gpgcheck=None + ): + config_parser.add_section(name) + if uri.startswith(os.sep) and os.path.exists(uri): + uri = 'file://{}'.format(uri) + config_parser.set(name, 'Server', uri) + config_parser.set( + name, 'SigLevel', '{0} {1}'.format( + 'Required' if pkg_gpgcheck else 'Never', + 'DatabaseRequired' if repo_gpgcheck else 'DatabaseNever' + ) + ) + + def add_repo( + self, name, uri, repo_type=None, + prio=None, dist=None, components=None, + user=None, secret=None, credentials_file=None, + repo_gpgcheck=None, pkg_gpgcheck=None, + sourcetype=None + ): + """ + Add pacman repository + + :param str name: repository base file name + :param str uri: repository URI + :param repo_type: unused + :param int prio: + :param dist: unused + :param components: components to be used from this repository + :param user: unused + :param secret: unused + :param credentials_file: unused + :param bool repo_gpgcheck: enable database signature validation + :param bool pkg_gpgcheck: enable package signature validation + :param str sourcetype: unused + """ + repo_file = '{0}/{1}.repo'.format( + self.shared_pacman_dir['repos-dir'], name + ) + self.repo_names.append(name + '.repo') + repo_config = ConfigParser() + repo_config.optionxform = str + if not components: + self._add_repo_section( + repo_config, name, uri, repo_gpgcheck, + pkg_gpgcheck + ) + else: + for component in components.split(): + self._add_repo_section( + repo_config, component, uri, repo_gpgcheck, + pkg_gpgcheck + ) + + with open(repo_file, 'w') as config: + repo_config.write(config) + + def import_trusted_keys(self, signing_keys): + """ + zypper runtime configuration and environment + """ + for signing_key in signing_keys: + if os.path.exists(signing_key): + Command.run( + ['pacman-key', '--recv-keys', signing_key] + ) + else: + Command.run( + ['pacman-key', '--add', signing_key] + ) + + def delete_repo(self, name): + """ + Delete pacman repository + + :param str name: repository name + """ + Path.wipe( + '{0}/{1}.repo'.format(self.shared_pacman_dir['repos-dir'], name) + ) + + def delete_all_repos(self): + """ + Delete all pacman repositories + """ + Path.wipe(self.shared_pacman_dir['repos-dir']) + Path.create(self.shared_pacman_dir['repos-dir']) + + def delete_repo_cache(self, name): + """ + Delete pacman repository cache + + The method deletes these directories to cleanup the + cache information + + :param str name: repository name + """ + Path.wipe(self.shared_pacman_dir['cache-dir']) + + def setup_package_database_configuration(self): + """ + Creates the folder that will contain the package manager database + """ + Path.create(os.sep.join([self.root_dir, 'var/lib/pacman'])) + + def cleanup_unused_repos(self): + """ + Delete unused pacman repositories + + Repository configurations which are not used for this build + must be removed otherwise they are taken into account for + the package installations + """ + repos_dir = self.shared_pacman_dir['repos-dir'] + repo_files = list(os.walk(repos_dir))[0][2] + for repo_file in repo_files: + if repo_file not in self.repo_names: + Path.wipe(repos_dir + '/' + repo_file) + + def _write_runtime_config(self): + runtime_pacman_config = ConfigParser() + runtime_pacman_config.optionxform = str + runtime_pacman_config.add_section('options') + + runtime_pacman_config.set( + 'options', 'Architecture', 'auto' + ) + runtime_pacman_config.set( + 'options', 'CacheDir', self.shared_pacman_dir['cache-dir'] + ) + + if self.check_signatures: + runtime_pacman_config.set( + 'options', 'SigLevel', 'Required DatabaseRequired' + ) + runtime_pacman_config.set( + 'options', 'LocalFileSigLevel', 'Required DatabaseRequired' + ) + else: + runtime_pacman_config.set( + 'options', 'SigLevel', 'Never DatabaseNever' + ) + runtime_pacman_config.set( + 'options', 'LocalFileSigLevel', 'Never DatabaseNever' + ) + + runtime_pacman_config.set( + 'options', 'Include', '{0}/*.repo'.format( + self.shared_pacman_dir['repos-dir'] + ) + ) + + with open(self.runtime_pacman_config_file.name, 'w') as config: + runtime_pacman_config.write(config) diff --git a/kiwi/schema/kiwi.rnc b/kiwi/schema/kiwi.rnc index df57ccd9..de37825f 100644 --- a/kiwi/schema/kiwi.rnc +++ b/kiwi/schema/kiwi.rnc @@ -793,7 +793,7 @@ div { # div { k.packagemanager.content = - "apt-get" | "zypper" | "yum" | "dnf" + "apt-get" | "zypper" | "yum" | "dnf" | "pacman" k.packagemanager.attlist = empty k.packagemanager = ## Name of the Package Manager diff --git a/kiwi/schema/kiwi.rng b/kiwi/schema/kiwi.rng index e4db2d7a..39c9adfd 100644 --- a/kiwi/schema/kiwi.rng +++ b/kiwi/schema/kiwi.rng @@ -1228,6 +1228,7 @@ the device is looked up in /dev/disk/by-* and /dev/mapper/* zypper yum dnf + pacman diff --git a/kiwi/system/setup.py b/kiwi/system/setup.py index f3b0d3ac..50c5a4da 100644 --- a/kiwi/system/setup.py +++ b/kiwi/system/setup.py @@ -499,6 +499,9 @@ class SystemSetup: elif packager == 'dpkg': self._export_deb_package_list(filename) return filename + elif packager == 'pacman': + self._export_pacman_package_list(filename) + return filename def export_package_verification(self, target_dir): """ @@ -1016,6 +1019,19 @@ class SystemSetup: ) packages.write(os.linesep) + def _export_pacman_package_list(self, filename): + log.info('Export pacman packages metadata') + query_call = Command.run(['pacman', '-Qe']) + with open(filename, 'w') as packages: + for line in query_call.output.splitlines(): + package, _, version_release = line.partition(' ') + version, _, release = version_release.partition('-') + packages.writelines([ + '{0}|None|{1}|{2}|None|None|None{3}'.format( + package, version, release, os.linesep + ) + ]) + def _export_rpm_package_verification(self, filename): log.info('Export rpm verification metadata') dbpath_option = [ diff --git a/package/python-kiwi-pkgbuild-template b/package/python-kiwi-pkgbuild-template new file mode 100644 index 00000000..a5176755 --- /dev/null +++ b/package/python-kiwi-pkgbuild-template @@ -0,0 +1,69 @@ +# Maintainer: Marcus Schaefer +# Maintainer: David Cassany + +pkgname=('python-kiwi' 'kiwi-man-pages' 'dracut-kiwi-lib' 'dracut-kiwi-oem-repart' 'dracut-kiwi-oem-dump' 'dracut-kiwi-live' 'dracut-kiwi-overlay') +arch=(x86_64) +pkgver=%%VERSION +pkgrel=0 +pkgdesc="KIWI - Appliance Builder Next Generation" +url="https://github.com/SUSE/kiwi/tarball/master" +license=('GPL3') +makedepends=(python-setuptools gcc shadow grep) +provides=(kiwi-ng kiwi) +source=("${pkgname}.tar.gz") +changelog="${pkgname}.changes" +md5sums=('%%MD5SUM') + + +build() { + cd kiwi-${pkgver} + python setup.py build +} + +package_python-kiwi(){ + depends=(python-docopt python-future python-lxml python-requests python-setuptools python-six python-pyxattr python-yaml grub qemu squashfs-tools gptfdisk pacman e2fsprogs xfsprogs btrfs-progs libisoburn lvm2 mtools parted multipath-tools rsync tar shadow kiwi-man-pages) + cd kiwi-${pkgver} + python setup.py install --root="${pkgdir}/" --optimize=1 --skip-build + ln -sr "${pkgdir}/usr/bin/kiwi-ng" "${pkgdir}/usr/bin/kiwi" + install -Dm644 LICENSE "$pkgdir/usr/share/licenses/$pkgname/LICENSE" +} + +package_kiwi-man-pages(){ + cd kiwi-${pkgver} + make buildroot="${pkgdir}/" docdir="/usr/share/doc/${pkgname}" install_package_docs +} + +package_dracut-kiwi-lib(){ + depends=(cryptsetup btrfs-progs gptfdisk coreutils e2fsprogs grep lvm2 mdadm parted util-linux xfsprogs dialog curl xz device-mapper dracut) + cd kiwi-${pkgver} + install -d -m 755 ${pkgdir}/usr/lib/dracut/modules.d/99kiwi-lib + cp -a dracut/modules.d/99kiwi-lib ${pkgdir}/usr/lib/dracut/modules.d/ +} + +package_dracut-kiwi-oem-repart(){ + depends=(dracut-kiwi-lib) + cd kiwi-${pkgver} + install -d -m 755 ${pkgdir}/usr/lib/dracut/modules.d/90kiwi-repart + cp -a dracut/modules.d/90kiwi-repart ${pkgdir}/usr/lib/dracut/modules.d/ +} + +package_dracut-kiwi-oem-dump(){ + depends=(dracut-kiwi-lib multipath-tools) + cd kiwi-${pkgver} + install -d -m 755 ${pkgdir}/usr/lib/dracut/modules.d/90kiwi-dump + cp -a dracut/modules.d/90kiwi-dump ${pkgdir}/usr/lib/dracut/modules.d/ +} + +package_dracut-kiwi-live(){ + depends=(dracut dialog xfsprogs e2fsprogs util-linux device-mapper libisoburn parted) + cd kiwi-${pkgver} + install -d -m 755 ${pkgdir}/usr/lib/dracut/modules.d/90kiwi-live + cp -a dracut/modules.d/90kiwi-live ${pkgdir}/usr/lib/dracut/modules.d/ +} + +package_dracut-kiwi-overlay(){ + depends=(dracut util-linux) + cd kiwi-${pkgver} + install -d -m 755 ${pkgdir}/usr/lib/dracut/modules.d/90kiwi-overlay + cp -a dracut/modules.d/90kiwi-overlay ${pkgdir}/usr/lib/dracut/modules.d/ +} diff --git a/test/unit/iso_tools/base_test.py b/test/unit/iso_tools/base_test.py index dabe0283..d70057a8 100644 --- a/test/unit/iso_tools/base_test.py +++ b/test/unit/iso_tools/base_test.py @@ -63,6 +63,10 @@ class TestIsoToolsBase: 'root_dir/usr/share/syslinux/isolinux.bin', 'root_dir/image/loader/' ), + call( + 'root_dir/usr/lib/syslinux/bios/isolinux.bin', + 'root_dir/image/loader/' + ), call( 'root_dir/usr/lib/syslinux/modules/bios/isolinux.bin', 'root_dir/image/loader/' @@ -75,6 +79,10 @@ class TestIsoToolsBase: 'root_dir/usr/share/syslinux/ldlinux.c32', 'root_dir/image/loader/' ), + call( + 'root_dir/usr/lib/syslinux/bios/ldlinux.c32', + 'root_dir/image/loader/' + ), call( 'root_dir/usr/lib/syslinux/modules/bios/ldlinux.c32', 'root_dir/image/loader/' @@ -87,6 +95,10 @@ class TestIsoToolsBase: 'root_dir/usr/share/syslinux/libcom32.c32', 'root_dir/image/loader/' ), + call( + 'root_dir/usr/lib/syslinux/bios/libcom32.c32', + 'root_dir/image/loader/' + ), call( 'root_dir/usr/lib/syslinux/modules/bios/libcom32.c32', 'root_dir/image/loader/' @@ -99,6 +111,10 @@ class TestIsoToolsBase: 'root_dir/usr/share/syslinux/libutil.c32', 'root_dir/image/loader/' ), + call( + 'root_dir/usr/lib/syslinux/bios/libutil.c32', + 'root_dir/image/loader/' + ), call( 'root_dir/usr/lib/syslinux/modules/bios/libutil.c32', 'root_dir/image/loader/' @@ -111,6 +127,10 @@ class TestIsoToolsBase: 'root_dir/usr/share/syslinux/gfxboot.c32', 'root_dir/image/loader/' ), + call( + 'root_dir/usr/lib/syslinux/bios/gfxboot.c32', + 'root_dir/image/loader/' + ), call( 'root_dir/usr/lib/syslinux/modules/bios/gfxboot.c32', 'root_dir/image/loader/' @@ -123,6 +143,10 @@ class TestIsoToolsBase: 'root_dir/usr/share/syslinux/gfxboot.com', 'root_dir/image/loader/' ), + call( + 'root_dir/usr/lib/syslinux/bios/gfxboot.com', + 'root_dir/image/loader/' + ), call( 'root_dir/usr/lib/syslinux/modules/bios/gfxboot.com', 'root_dir/image/loader/' @@ -135,6 +159,10 @@ class TestIsoToolsBase: 'root_dir/usr/share/syslinux/menu.c32', 'root_dir/image/loader/' ), + call( + 'root_dir/usr/lib/syslinux/bios/menu.c32', + 'root_dir/image/loader/' + ), call( 'root_dir/usr/lib/syslinux/modules/bios/menu.c32', 'root_dir/image/loader/' @@ -147,6 +175,10 @@ class TestIsoToolsBase: 'root_dir/usr/share/syslinux/chain.c32', 'root_dir/image/loader/' ), + call( + 'root_dir/usr/lib/syslinux/bios/chain.c32', + 'root_dir/image/loader/' + ), call( 'root_dir/usr/lib/syslinux/modules/bios/chain.c32', 'root_dir/image/loader/' @@ -159,6 +191,10 @@ class TestIsoToolsBase: 'root_dir/usr/share/syslinux/mboot.c32', 'root_dir/image/loader/' ), + call( + 'root_dir/usr/lib/syslinux/bios/mboot.c32', + 'root_dir/image/loader/' + ), call( 'root_dir/usr/lib/syslinux/modules/bios/mboot.c32', 'root_dir/image/loader/' diff --git a/test/unit/iso_tools/xorriso_test.py b/test/unit/iso_tools/xorriso_test.py index a2df0762..95035165 100644 --- a/test/unit/iso_tools/xorriso_test.py +++ b/test/unit/iso_tools/xorriso_test.py @@ -37,6 +37,7 @@ class TestIsoToolsXorrIso: mock_which.assert_called_once_with( 'isohdpfx.bin', [ '/usr/share/syslinux', + '/usr/lib/syslinux/bios', '/usr/lib/syslinux/modules/bios', '/usr/lib/ISOLINUX' ] diff --git a/test/unit/package_manager/init_test.py b/test/unit/package_manager/init_test.py index 5f3ebbc7..f3d852f0 100644 --- a/test/unit/package_manager/init_test.py +++ b/test/unit/package_manager/init_test.py @@ -36,3 +36,9 @@ class TestPackageManager: repository = Mock() PackageManager(repository, 'apt-get') mock_manager.assert_called_once_with(repository, None) + + @patch('kiwi.package_manager.PackageManagerPacman') + def test_manager_pacman(self, mock_manager): + repository = Mock() + PackageManager(repository, 'pacman') + mock_manager.assert_called_once_with(repository, None) diff --git a/test/unit/package_manager/pacman_test.py b/test/unit/package_manager/pacman_test.py new file mode 100644 index 00000000..9aceff41 --- /dev/null +++ b/test/unit/package_manager/pacman_test.py @@ -0,0 +1,137 @@ +from mock import patch, call +from pytest import raises +import mock + +from kiwi.package_manager.pacman import PackageManagerPacman + +from kiwi.exceptions import KiwiRequestError + + +class TestPackageManagerPacman: + def setup(self): + repository = mock.Mock() + repository.root_dir = '/root-dir' + + repository.runtime_config = mock.Mock( + return_value={ + 'pacman_args': [ + '--config', '/root-dir/pacman.conf', '-y', '--noconfirm' + ], + 'command_env': ['env'] + } + ) + self.manager = PackageManagerPacman(repository) + + def test_request_package(self): + self.manager.request_package('name') + assert self.manager.package_requests == ['name'] + + def test_request_collection(self): + self.manager.request_collection('name') + assert self.manager.collection_requests == [] + assert self.manager.package_requests == ['name'] + + def test_request_product(self): + self.manager.request_product('name') + assert self.manager.product_requests == [] + + def test_request_package_exclusion(self): + self.manager.request_package_exclusion('name') + assert self.manager.exclude_requests == ['name'] + + @patch('kiwi.command.Command.call') + @patch('kiwi.command.Command.run') + def test_process_install_requests_bootstrap(self, mock_run, mock_call): + self.manager.request_package('vim') + self.manager.request_collection('collection') + self.manager.process_install_requests_bootstrap() + mock_run.call_args_list == [ + call(['rm', '-r', '-f', '/root-dir/var/run']), + call([ + 'pacman', '--config', '/root-dir/pacman.conf', '-y', + '--noconfirm', '--needed', '--root', '/root-dir', '-Sy' + ]) + ] + mock_call.assert_called_once_with( + [ + 'bash', '-c', + 'pacman --config /root-dir/pacman.conf -y --noconfirm ' + '--root /root-dir -S --needed --overwrite /root-dir/var/run ' + 'vim collection' + ], ['env'] + ) + + @patch('kiwi.command.Command.call') + def test_process_install_requests(self, mock_call): + self.manager.request_package('vim') + self.manager.request_collection('collection') + self.manager.request_package_exclusion('skipme') + self.manager.process_install_requests() + mock_call.assert_called_once_with( + [ + 'bash', '-c', + 'chroot /root-dir pacman --config /pacman.conf -y ' + '--noconfirm -S --needed vim collection' + ], ['env'] + ) + + @patch('kiwi.command.Command.call') + @patch('kiwi.command.Command.run') + def test_process_delete_requests_force(self, mock_run, mock_call): + self.manager.request_package('vim') + self.manager.process_delete_requests(True) + mock_call.assert_called_once_with( + [ + 'chroot', '/root-dir', 'pacman', '--config', '/pacman.conf', + '-y', '--noconfirm', '-Rdd', 'vim' + ], ['env'] + ) + + @patch('kiwi.command.Command.call') + @patch('kiwi.command.Command.run') + def test_process_delete_requests_no_force(self, mock_run, mock_call): + self.manager.request_package('vim') + self.manager.process_delete_requests() + mock_call.assert_called_once_with( + [ + 'chroot', '/root-dir', 'pacman', '--config', '/pacman.conf', + '-y', '--noconfirm', '-Rs', 'vim' + ], ['env'] + ) + + @patch('kiwi.command.Command.run') + @patch('kiwi.command.Command.call') + def test_process_delete_requests_package_missing( + self, mock_call, mock_run + ): + mock_run.side_effect = Exception + self.manager.request_package('vim') + with raises(KiwiRequestError): + self.manager.process_delete_requests() + mock_run.assert_called_once_with( + ['chroot', '/root-dir', 'pacman', '-Qi', 'vim'] + ) + + def test_process_only_required(self): + self.manager.process_only_required() + assert self.manager.custom_args == [] + + def test_process_plus_recommended(self): + self.manager.process_plus_recommended() + assert self.manager.custom_args == [] + + @patch('kiwi.command.Command.call') + def test_update(self, mock_call): + self.manager.update() + mock_call.assert_called_once_with( + [ + 'chroot', '/root-dir', 'pacman', '--config', '/pacman.conf', + '-y', '--noconfirm', '-Su' + ], ['env'] + ) + + def test_match_package_installed(self): + assert self.manager.match_package_installed('foo', ' installing foo') + + def test_match_package_deleted(self): + assert self.manager.match_package_deleted('foo', ' removing foo') diff --git a/test/unit/repository/init_test.py b/test/unit/repository/init_test.py index 343b404d..8299fa01 100644 --- a/test/unit/repository/init_test.py +++ b/test/unit/repository/init_test.py @@ -34,3 +34,9 @@ class TestRepository: root_bind = mock.Mock() Repository(root_bind, 'apt-get') mock_manager.assert_called_once_with(root_bind, None) + + @patch('kiwi.repository.RepositoryPacman') + def test_repository_pacman(self, mock_manager): + root_bind = mock.Mock() + Repository(root_bind, 'pacman') + mock_manager.assert_called_once_with(root_bind, None) diff --git a/test/unit/repository/pacman_test.py b/test/unit/repository/pacman_test.py new file mode 100644 index 00000000..83a1d3fe --- /dev/null +++ b/test/unit/repository/pacman_test.py @@ -0,0 +1,191 @@ + +from mock import ( + patch, Mock, call, mock_open +) + +import os + +from kiwi.repository.pacman import RepositoryPacman + + +class TestRepositorPacman(object): + @patch('kiwi.repository.pacman.NamedTemporaryFile') + @patch('kiwi.repository.pacman.ConfigParser') + @patch('kiwi.repository.pacman.Path.create') + def setup(self, mock_path, mock_config, mock_temp): + runtime_pacman_config = Mock() + mock_config.return_value = runtime_pacman_config + tmpfile = Mock() + tmpfile.name = 'tmpfile' + mock_temp.return_value = tmpfile + root_bind = Mock() + root_bind.move_to_root = Mock( + return_value=['root-moved-arguments'] + ) + root_bind.root_dir = '../data' + root_bind.shared_location = '/shared-dir' + with patch('builtins.open', create=True): + self.repo = RepositoryPacman(root_bind) + + assert runtime_pacman_config.set.call_args_list == [ + call('options', 'Architecture', 'auto'), + call('options', 'CacheDir', '/shared-dir/pacman/cache'), + call('options', 'SigLevel', 'Never DatabaseNever'), + call('options', 'LocalFileSigLevel', 'Never DatabaseNever'), + call('options', 'Include', '/shared-dir/pacman/repos/*.repo') + ] + + @patch('kiwi.repository.pacman.NamedTemporaryFile') + @patch('kiwi.repository.pacman.Path.create') + def test_post_init_with_custom_args(self, mock_path, mock_temp): + self.repo.post_init(['check_signatures']) + assert self.repo.custom_args == [] + assert self.repo.check_signatures + + def test_runtime_config(self): + assert self.repo.runtime_config()['pacman_args'] == \ + self.repo.pacman_args + assert self.repo.runtime_config()['command_env'] == \ + os.environ + + @patch('kiwi.repository.pacman.ConfigParser') + @patch('os.path.exists') + def test_add_local_repo_with_components( + self, mock_exists, mock_config + ): + repo_config = Mock() + mock_config.return_value = repo_config + mock_exists.return_value = True + + m_open = mock_open() + with patch('builtins.open', m_open, create=True): + self.repo.add_repo( + 'foo', '/some_uri', components='core extra', + repo_gpgcheck=False, pkg_gpgcheck=True + ) + m_open.assert_called_once_with( + '/shared-dir/pacman/repos/foo.repo', 'w' + ) + + assert repo_config.add_section.call_args_list == [ + call('core'), call('extra') + ] + assert repo_config.set.call_args_list == [ + call('core', 'Server', 'file:///some_uri'), + call('core', 'SigLevel', 'Required DatabaseNever'), + call('extra', 'Server', 'file:///some_uri'), + call('extra', 'SigLevel', 'Required DatabaseNever'), + ] + + @patch('kiwi.repository.pacman.ConfigParser') + @patch('os.path.exists') + def test_add_repo_with_components( + self, mock_exists, mock_config + ): + repo_config = Mock() + mock_config.return_value = repo_config + mock_exists.return_value = True + + m_open = mock_open() + with patch('builtins.open', m_open, create=True): + self.repo.add_repo( + 'foo', 'some_uri', components='core extra', + repo_gpgcheck=False, pkg_gpgcheck=True + ) + m_open.assert_called_once_with( + '/shared-dir/pacman/repos/foo.repo', 'w' + ) + + assert repo_config.add_section.call_args_list == [ + call('core'), call('extra') + ] + assert repo_config.set.call_args_list == [ + call('core', 'Server', 'some_uri'), + call('core', 'SigLevel', 'Required DatabaseNever'), + call('extra', 'Server', 'some_uri'), + call('extra', 'SigLevel', 'Required DatabaseNever'), + ] + + @patch('kiwi.repository.pacman.ConfigParser') + @patch('os.path.exists') + def test_add_repo_without_components( + self, mock_exists, mock_config + ): + repo_config = Mock() + mock_config.return_value = repo_config + mock_exists.return_value = True + + m_open = mock_open() + with patch('builtins.open', m_open, create=True): + self.repo.add_repo('foo', 'some_uri', pkg_gpgcheck=False) + + m_open.assert_called_once_with( + '/shared-dir/pacman/repos/foo.repo', 'w' + ) + + assert repo_config.add_section.call_args_list == [ + call('foo') + ] + assert repo_config.set.call_args_list == [ + call('foo', 'Server', 'some_uri'), + call('foo', 'SigLevel', 'Never DatabaseNever'), + ] + + @patch('kiwi.repository.pacman.Path.create') + def test_setup_package_database_configuration(self, mock_path_create): + self.repo.setup_package_database_configuration() + mock_path_create.assert_called_once_with( + '../data/var/lib/pacman' + ) + + @patch('kiwi.repository.pacman.os.path.exists') + @patch('kiwi.repository.pacman.Command.run') + def test_import_trusted_keys(self, mock_cmd, mock_exists): + exists = [True, False] + mock_exists.side_effect = lambda x: exists.pop() + signing_keys = ['key-file-a.asc', 'key-file-b_ID'] + self.repo.import_trusted_keys(signing_keys) + assert mock_cmd.call_args_list == [ + call(['pacman-key', '--add', 'key-file-a.asc']), + call(['pacman-key', '--recv-keys', 'key-file-b_ID']) + ] + + @patch('kiwi.repository.pacman.Path') + def test_delete_repo(self, mock_path): + self.repo.delete_repo('foo') + mock_path.wipe.assert_called_once_with( + '/shared-dir/pacman/repos/foo.repo' + ) + + @patch('kiwi.repository.pacman.Path') + def test_delete_all_repos(self, mock_path): + self.repo.delete_all_repos() + mock_path.wipe.assert_called_once_with('/shared-dir/pacman/repos') + mock_path.create.assert_called_once_with('/shared-dir/pacman/repos') + + @patch('kiwi.repository.pacman.Path.wipe') + @patch('kiwi.repository.pacman.os.walk') + def test_cleanup_unused_repos(self, mock_os_walk, mock_wipe): + mock_os_walk.return_value = [( + '/shared-dir/pacman/repos', + [], + ['foo.repo', 'bar.repo'] + )] + self.repo.cleanup_unused_repos() + mock_os_walk.assert_called_once_with('/shared-dir/pacman/repos') + assert mock_wipe.call_args_list == [ + call('/shared-dir/pacman/repos/foo.repo'), + call('/shared-dir/pacman/repos/bar.repo') + ] + + def test_use_default_location(self): + self.repo.use_default_location() + assert self.repo.shared_pacman_dir['cache-dir'] == \ + '/shared-dir/pacman/cache' + assert self.repo.shared_pacman_dir['repos-dir'] == \ + '../data/etc/pacman.d' + + @patch('kiwi.repository.pacman.Path') + def test_delete_repo_cache(self, mock_path): + self.repo.delete_repo_cache('foo') + mock_path.wipe.assert_called_once_with('/shared-dir/pacman/cache') diff --git a/test/unit/system/setup_test.py b/test/unit/system/setup_test.py index 5fb94ffd..ab5f4b02 100644 --- a/test/unit/system/setup_test.py +++ b/test/unit/system/setup_test.py @@ -924,6 +924,24 @@ class TestSystemSetup: '-f', '${Package}|None|${Version}|None|${Architecture}|None|None\\n' ]) + @patch('kiwi.system.setup.Command.run') + def test_export_package_list_pacman(self, mock_command): + command = Mock() + command.output = 'packages_data' + mock_command.return_value = command + self.xml_state.get_package_manager = Mock( + return_value='pacman' + ) + + with patch('builtins.open') as m_open: + result = self.setup.export_package_list('target_dir') + m_open.assert_called_once_with( + 'target_dir/some-image.x86_64-1.2.3.packages', 'w' + ) + + assert result == 'target_dir/some-image.x86_64-1.2.3.packages' + mock_command.assert_called_once_with(['pacman', '-Qe']) + @patch('kiwi.system.setup.Command.run') @patch('kiwi.system.setup.RpmDataBase') @patch('kiwi.system.setup.MountManager')