From 5c217afa00cd13533538ed079ab2b97b14160c9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marcus=20Sch=C3=A4fer?= Date: Mon, 13 Jan 2025 14:45:55 +0100 Subject: [PATCH] Add support for reading optional pkgmgr env file If there is a file .kiwi.package_manager.env in the root of the image tree it will be read and put into the caller environment for the selected package and repository manager. There are features in e.g zypper which can only be used via env variables. This Fixes bsc#1235448 --- kiwi/defaults.py | 3 ++ kiwi/exceptions.py | 7 +++++ kiwi/repository/apt.py | 3 ++ kiwi/repository/dnf4.py | 3 ++ kiwi/repository/dnf5.py | 3 ++ kiwi/repository/pacman.py | 3 ++ kiwi/repository/zypper.py | 3 ++ kiwi/utils/toenv.py | 59 +++++++++++++++++++++++++++++++++++ test/data/some.env | 1 + test/data/some_broken.env | 1 + test/unit/utils/toenv_test.py | 18 +++++++++++ 11 files changed, 104 insertions(+) create mode 100644 kiwi/utils/toenv.py create mode 100644 test/data/some.env create mode 100644 test/data/some_broken.env create mode 100644 test/unit/utils/toenv_test.py diff --git a/kiwi/defaults.py b/kiwi/defaults.py index fc7607e476e..274c20db9fa 100644 --- a/kiwi/defaults.py +++ b/kiwi/defaults.py @@ -96,6 +96,9 @@ PLATFORM_MACHINE = platform.machine() EFI_FAT_IMAGE_SIZE = 20 +# optional package manager environment variables +PACKAGE_MANAGER_ENV_VARS = '/.kiwi.package_manager.env' + log = logging.getLogger('kiwi') diff --git a/kiwi/exceptions.py b/kiwi/exceptions.py index 63f8e85fdf9..7f91736d355 100644 --- a/kiwi/exceptions.py +++ b/kiwi/exceptions.py @@ -444,6 +444,13 @@ class KiwiOSReleaseImportError(KiwiError): """ +class KiwiEnvImportError(KiwiError): + """ + Exception raised if extending os.environ with another + env file caused an issue + """ + + class KiwiPackageManagerSetupError(KiwiError): """ Exception raised if an attempt was made to create a package diff --git a/kiwi/repository/apt.py b/kiwi/repository/apt.py index 529f9d00a40..54896e64cc2 100644 --- a/kiwi/repository/apt.py +++ b/kiwi/repository/apt.py @@ -21,6 +21,7 @@ from typing import List, Dict # project +import kiwi.defaults as defaults from kiwi.utils.temporary import ( Temporary, TmpT ) @@ -28,6 +29,7 @@ from kiwi.repository.base import RepositoryBase from kiwi.path import Path from kiwi.command import Command +from kiwi.utils.toenv import ToEnv log = logging.getLogger('kiwi') @@ -306,6 +308,7 @@ def _add_components(self, components: str) -> None: def _create_apt_get_runtime_environment(self) -> Dict: for apt_get_dir in list(self.shared_apt_get_dir.values()): Path.create(apt_get_dir) + ToEnv(self.root_dir, defaults.PACKAGE_MANAGER_ENV_VARS) return dict( os.environ, LANG='C', DEBIAN_FRONTEND='noninteractive' ) diff --git a/kiwi/repository/dnf4.py b/kiwi/repository/dnf4.py index 5136ccaff4f..a3cf44dd883 100644 --- a/kiwi/repository/dnf4.py +++ b/kiwi/repository/dnf4.py @@ -21,6 +21,7 @@ from typing import List, Dict # project +import kiwi.defaults as defaults from kiwi.utils.temporary import ( Temporary, TmpT ) @@ -29,6 +30,7 @@ from kiwi.repository.base import RepositoryBase from kiwi.path import Path from kiwi.utils.rpm_database import RpmDataBase +from kiwi.utils.toenv import ToEnv class RepositoryDnf4(RepositoryBase): @@ -315,6 +317,7 @@ def cleanup_unused_repos(self) -> None: def _create_dnf_runtime_environment(self) -> Dict: for dnf_dir in list(self.shared_dnf_dir.values()): Path.create(dnf_dir) + ToEnv(self.root_dir, defaults.PACKAGE_MANAGER_ENV_VARS) return dict( os.environ, LANG='C' ) diff --git a/kiwi/repository/dnf5.py b/kiwi/repository/dnf5.py index 45b8cef3db1..d49e6b2cfaa 100644 --- a/kiwi/repository/dnf5.py +++ b/kiwi/repository/dnf5.py @@ -21,6 +21,7 @@ from typing import List, Dict # project +import kiwi.defaults as defaults from kiwi.utils.temporary import ( Temporary, TmpT ) @@ -29,6 +30,7 @@ from kiwi.repository.base import RepositoryBase from kiwi.path import Path from kiwi.utils.rpm_database import RpmDataBase +from kiwi.utils.toenv import ToEnv class RepositoryDnf5(RepositoryBase): @@ -315,6 +317,7 @@ def cleanup_unused_repos(self) -> None: def _create_dnf_runtime_environment(self) -> Dict: for dnf_dir in list(self.shared_dnf_dir.values()): Path.create(dnf_dir) + ToEnv(self.root_dir, defaults.PACKAGE_MANAGER_ENV_VARS) return dict( os.environ, LANG='C' ) diff --git a/kiwi/repository/pacman.py b/kiwi/repository/pacman.py index 60946ab757d..9059bff5435 100644 --- a/kiwi/repository/pacman.py +++ b/kiwi/repository/pacman.py @@ -22,12 +22,14 @@ ) # project +import kiwi.defaults as defaults from kiwi.utils.temporary import ( Temporary, TmpT ) from kiwi.repository.base import RepositoryBase from kiwi.path import Path from kiwi.command import Command +from kiwi.utils.toenv import ToEnv class RepositoryPacman(RepositoryBase): @@ -90,6 +92,7 @@ def runtime_config(self) -> Dict: """ pacman runtime configuration and environment """ + ToEnv(self.root_dir, defaults.PACKAGE_MANAGER_ENV_VARS) return { 'pacman_args': self.pacman_args, 'command_env': os.environ diff --git a/kiwi/repository/zypper.py b/kiwi/repository/zypper.py index 4fc4a117f5e..2bedc3de647 100644 --- a/kiwi/repository/zypper.py +++ b/kiwi/repository/zypper.py @@ -21,6 +21,7 @@ from typing import List, Dict # project +import kiwi.defaults as defaults from kiwi.utils.temporary import ( Temporary, TmpT ) @@ -30,6 +31,7 @@ from kiwi.system.uri import Uri from kiwi.path import Path from kiwi.utils.rpm_database import RpmDataBase +from kiwi.utils.toenv import ToEnv class RepositoryZypper(RepositoryBase): @@ -427,6 +429,7 @@ def cleanup_unused_repos(self) -> None: def _create_zypper_runtime_environment(self) -> Dict: for zypper_dir in list(self.shared_zypper_dir.values()): Path.create(zypper_dir) + ToEnv(self.root_dir, defaults.PACKAGE_MANAGER_ENV_VARS) return dict( os.environ, LANG='C', diff --git a/kiwi/utils/toenv.py b/kiwi/utils/toenv.py new file mode 100644 index 00000000000..af8c0c4de36 --- /dev/null +++ b/kiwi/utils/toenv.py @@ -0,0 +1,59 @@ +# Copyright (c) 2024 SUSE Software Solutions Germany 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 csv +import os +from io import TextIOWrapper +from typing import Iterable + +# project +from kiwi.exceptions import KiwiEnvImportError + + +class ToEnv: + """ + **Read env file and merge with os.environ** + """ + def __init__(self, root_dir: str, envfile: str): + self.data = {} + env_file = f'{root_dir}/{envfile}' + if os.path.isfile(env_file): + try: + with open(env_file) as osdata: + reader = csv.reader(ToEnv._rip(osdata), delimiter='=') + self.data = dict(reader) + except Exception as issue: + raise KiwiEnvImportError( + f'Import of {envfile} failed with {issue}' + ) + if self.data: + os.environ.update(self.data) + + @staticmethod + def _is_comment(line: str) -> bool: + return line.startswith('#') + + @staticmethod + def _is_whitespace(line: str) -> bool: + return line.isspace() + + @staticmethod + def _rip(csvfile: TextIOWrapper) -> Iterable[str]: + for row in csvfile: + if not ToEnv._is_comment(row) \ + and not ToEnv._is_whitespace(row): + yield row diff --git a/test/data/some.env b/test/data/some.env new file mode 100644 index 00000000000..87e24d4ecef --- /dev/null +++ b/test/data/some.env @@ -0,0 +1 @@ +ZYPP_MODALIAS_SYSFS="" diff --git a/test/data/some_broken.env b/test/data/some_broken.env new file mode 100644 index 00000000000..63fc8131d56 --- /dev/null +++ b/test/data/some_broken.env @@ -0,0 +1 @@ +xxxx diff --git a/test/unit/utils/toenv_test.py b/test/unit/utils/toenv_test.py new file mode 100644 index 00000000000..db6dbf1a57e --- /dev/null +++ b/test/unit/utils/toenv_test.py @@ -0,0 +1,18 @@ +from pytest import raises +import os + +from kiwi.utils.toenv import ToEnv +from kiwi.exceptions import KiwiEnvImportError + + +class TestToEnv(object): + def setup(self): + ToEnv('../data', 'some.env') + assert os.environ['ZYPP_MODALIAS_SYSFS'] == '' + + def setup_method(self, cls): + self.setup() + + def test_setup_raises(self): + with raises(KiwiEnvImportError): + ToEnv('../data', 'some_broken.env')