diff --git a/.github/workflows/bump_version.yml b/.github/workflows/bump_version.yml index fdb915a..95e453b 100644 --- a/.github/workflows/bump_version.yml +++ b/.github/workflows/bump_version.yml @@ -8,10 +8,9 @@ jobs: runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 - with: - python-version: 3.7 - - run: pip3 install --upgrade pip wheel + - run: | + sudo apt-get install python3-venv make + pip3 install --upgrade pip wheel - run: echo bldr_version=$(make bump-version) | tee --append $GITHUB_ENV - uses: peter-evans/create-pull-request@v5 with: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 316b4c4..f9fc299 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,10 +14,9 @@ jobs: runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 - with: - python-version: 3.7 - - run: pip3 install --upgrade pip + - run: | + sudo apt-get install python3-venv make + pip3 install --upgrade pip - run: make dev - run: make cs lint @@ -26,12 +25,16 @@ jobs: runs-on: ubuntu-20.04 strategy: matrix: - docker-image: ['ubuntu:xenial', 'ubuntu:bionic', 'ubuntu:focal', 'debian:bullseye'] + docker-image: + - ubuntu:focal + - ubuntu:jammy + - ubuntu:noble + - debian:bullseye + - debian:bookworm steps: - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 - with: - python-version: 3.7 - - run: pip3 install --upgrade pip + - run: | + sudo apt-get install python3-venv make + pip3 install --upgrade pip - run: make DOCKER_IMAGE=${{ matrix.docker-image }} check-docker-image diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5e3890a..abbcbc1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,10 +8,9 @@ jobs: runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v3 - - uses: actions/setup-python@v4 - with: - python-version: 3.7 - - run: pip3 install --upgrade pip wheel twine + - run: | + sudo apt-get install python3-venv make + pip3 install --upgrade pip wheel twine - run: make release - run: make release-test - name: Publish a Python distribution to PyPI diff --git a/Makefile b/Makefile index 8816017..a5629c5 100644 --- a/Makefile +++ b/Makefile @@ -41,7 +41,7 @@ check-docker-image: dev # Run tests on one selected docker image .PHONY: quick-check quick-check: - . $(VIRTUALENV)/bin/activate && pytest --docker-image=ubuntu:bionic + . $(VIRTUALENV)/bin/activate && pytest --docker-image=ubuntu:focal # Update requirements files for setup.py .PHONY: update-requirements @@ -68,7 +68,7 @@ release: .PHONY: release-test release-test: release - . $(RELEASE_TEST_VENV)/bin/activate && pytest --docker-image=ubuntu:bionic + . $(RELEASE_TEST_VENV)/bin/activate && pytest --docker-image=ubuntu:focal # Install development dependencies (for testing) in virtualenv .PHONY: dev diff --git a/bldr/bldr.py b/bldr/bldr.py index 2b15a81..50fa092 100644 --- a/bldr/bldr.py +++ b/bldr/bldr.py @@ -1,3 +1,4 @@ +import importlib_resources import logging import os import pwd @@ -10,7 +11,7 @@ from tempfile import TemporaryDirectory from .docker_utils import create_docker_client, DockerImageBuilder, DockerImage, DockerContainer, DEFAULT_DOCKER_TIMEOUT -from .utils import BLDRError, BLDRSetupFailed, escape_docker_image_tag, get_resource +from .utils import BLDRError, BLDRSetupFailed, escape_docker_image_tag PRE_BUILD_HOOK = "/hooks/pre-build" @@ -106,7 +107,10 @@ def _build_image(self, tag: str, control_file: Optional[Path] = None) -> DockerI with TemporaryDirectory(prefix="bldr_docker_dir_") as tmp_dir: docker_files_dir = Path(tmp_dir).joinpath('docker_files') - shutil.copytree(str(get_resource('.')), str(docker_files_dir)) + with importlib_resources.as_file( + importlib_resources.files().joinpath('data') + ) as resource_dir: + shutil.copytree(resource_dir, docker_files_dir) if control_file is None: docker_files_dir.joinpath('control').write_text('') else: @@ -201,7 +205,7 @@ def build(self, package_dir: Path) -> DockerImage: @classmethod def selftest(cls) -> None: client = create_docker_client() - for ubuntu_release in ['xenial', 'bionic', 'focal']: + for ubuntu_release in ['focal', 'jammy']: image = DockerImage(client=client, image='ubuntu:{}'.format(ubuntu_release)) assert image is not None, "DockerImage should be initialized without Exception" diff --git a/bldr/cli.py b/bldr/cli.py index 87e7b4d..44975a9 100644 --- a/bldr/cli.py +++ b/bldr/cli.py @@ -60,7 +60,7 @@ def _add_common_arguments(cls, parser: argparse.ArgumentParser) -> None: parser.add_argument( "docker_from", - help="Specify the value which will be used for the FROM keyword when building the docker image. Example: 'ubuntu:bionic'", + help="Specify the value which will be used for the FROM keyword when building the docker image. Example: 'ubuntu:jammy'", ) parser.add_argument( '-s', '--snapshot', diff --git a/bldr/config.py b/bldr/config.py index 94a8a32..7c1ecf5 100644 --- a/bldr/config.py +++ b/bldr/config.py @@ -78,7 +78,7 @@ def __init__(self, *args: Any, **kwargs: Any): super().__init__(*args, **kwargs) - def parse_known_args(self, args: Optional[Sequence[str]] = None, namespace: Optional[argparse.Namespace] = None) -> Tuple[argparse.Namespace, List[str]]: + def parse_known_args(self, args: Optional[Sequence[str]] = None, namespace=None): if self.config_loader: self.set_defaults_from_config(args) return super().parse_known_args(args, namespace=None) diff --git a/bldr/utils.py b/bldr/utils.py index c794a8d..f452870 100644 --- a/bldr/utils.py +++ b/bldr/utils.py @@ -1,7 +1,6 @@ import os import pwd from pathlib import Path -from pkg_resources import resource_filename class BLDRError(Exception): @@ -19,10 +18,6 @@ def __init__(self, msg: str, exitcode: int = 1) -> None: super().__init__(msg, exitcode) -def get_resource(path: str) -> Path: - return Path(resource_filename('bldr', str(Path('data', path)))) - - def escape_docker_image_tag(tag: str) -> str: return tag.replace(":", "-").replace("/", "-") diff --git a/requirements-dev.in b/requirements-dev.in index e8fe490..62b6940 100644 --- a/requirements-dev.in +++ b/requirements-dev.in @@ -1,5 +1,5 @@ autopep8 -flake8>=5 # for type annotation support and Python 3.8 compatibility in pyflakes 2.1.0+ +flake8 mypy pycodestyle pytest diff --git a/requirements-dev.txt b/requirements-dev.txt index 0bff98f..313ae79 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,52 +1,46 @@ # -# This file is autogenerated by pip-compile with Python 3.7 +# This file is autogenerated by pip-compile with Python 3.8 # by the following command: # -# pip-compile --no-emit-index-url --no-emit-trusted-host --output-file=requirements-dev.txt --resolver=backtracking requirements-dev.in +# pip-compile --no-emit-index-url --no-emit-trusted-host --output-file=requirements-dev.txt requirements-dev.in # -autopep8==2.0.0 +autopep8==2.3.1 # via -r requirements-dev.in -coverage[toml]==7.2.7 - # via - # coverage - # pytest-cov +coverage[toml]==7.6.0 + # via pytest-cov exceptiongroup==1.2.2 # via pytest -flake8==5.0.4 +flake8==7.1.0 # via -r requirements-dev.in -importlib-metadata==4.2.0 - # via - # flake8 - # pluggy - # pytest - # pytest-console-scripts +importlib-metadata==8.0.0 + # via pytest-console-scripts iniconfig==2.0.0 # via pytest mccabe==0.7.0 # via flake8 -mypy==1.4.1 +mypy==1.10.1 # via -r requirements-dev.in mypy-extensions==1.0.0 # via mypy -packaging==24.0 +packaging==24.1 # via pytest -pluggy==1.2.0 +pluggy==1.5.0 # via pytest -pycodestyle==2.9.1 +pycodestyle==2.12.0 # via # -r requirements-dev.in # autopep8 # flake8 -pyflakes==2.5.0 +pyflakes==3.2.0 # via flake8 -pytest==7.4.4 +pytest==8.2.2 # via # -r requirements-dev.in # pytest-console-scripts # pytest-cov -pytest-console-scripts==1.4.0 +pytest-console-scripts==1.4.1 # via -r requirements-dev.in -pytest-cov==4.1.0 +pytest-cov==5.0.0 # via -r requirements-dev.in tomli==2.0.1 # via @@ -54,17 +48,13 @@ tomli==2.0.1 # coverage # mypy # pytest -typed-ast==1.5.5 - # via mypy -types-requests==2.31.0.20231231 +types-requests==2.32.0.20240712 # via -r requirements-dev.in -types-setuptools==69.0.0.0 +types-setuptools==70.3.0.20240710 # via -r requirements-dev.in -typing-extensions==4.7.1 - # via - # importlib-metadata - # mypy -urllib3==2.0.7 +typing-extensions==4.12.2 + # via mypy +urllib3==2.2.2 # via types-requests -zipp==3.15.0 +zipp==3.19.2 # via importlib-metadata diff --git a/requirements.in b/requirements.in index 97c0281..880f6b5 100644 --- a/requirements.in +++ b/requirements.in @@ -1,2 +1,3 @@ docker dockerpty +importlib_resources diff --git a/requirements.txt b/requirements.txt index 1a89a96..84cd493 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,28 +1,28 @@ # -# This file is autogenerated by pip-compile with Python 3.7 +# This file is autogenerated by pip-compile with Python 3.8 # by the following command: # -# pip-compile --no-emit-index-url --no-emit-trusted-host --output-file=requirements.txt --resolver=backtracking requirements.in +# pip-compile --no-emit-index-url --no-emit-trusted-host --output-file=requirements.txt requirements.in # certifi==2024.7.4 # via requests charset-normalizer==3.3.2 # via requests -docker==6.1.3 +docker==7.1.0 # via -r requirements.in dockerpty==0.4.1 # via -r requirements.in idna==3.7 # via requests -packaging==24.0 - # via docker -requests==2.31.0 +importlib-resources==6.4.0 + # via -r requirements.in +requests==2.32.3 # via docker six==1.16.0 # via dockerpty -urllib3==2.0.7 +urllib3==2.2.2 # via # docker # requests -websocket-client==1.6.1 - # via docker +zipp==3.19.2 + # via importlib-resources diff --git a/setup.py b/setup.py index af26018..0f79a19 100755 --- a/setup.py +++ b/setup.py @@ -38,7 +38,7 @@ def get_package_data(): extras_require={ 'dev': Path('requirements-dev.txt').read_text(), }, - python_requires=">=3.7", + python_requires=">=3.8", package_data={'bldr': [ 'VERSION', ] + get_package_data()}, @@ -50,10 +50,11 @@ def get_package_data(): "Operating System :: POSIX :: Linux", "Programming Language :: Python", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Topic :: System :: Systems Administration", "Topic :: Utilities", ], diff --git a/test/conftest.py b/test/conftest.py index 5c7e90c..8e88713 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -2,7 +2,7 @@ import shutil from pathlib import Path -DEFAULT_DOCKER_IMAGES = ["ubuntu:xenial", "ubuntu:bionic", "ubuntu:focal", "debian:bullseye"] +DEFAULT_DOCKER_IMAGES = ["ubuntu:focal", "ubuntu:jammy"] def pytest_addoption(parser): diff --git a/test/e2e/test_cli.py b/test/e2e/test_cli.py index b981909..02ec037 100644 --- a/test/e2e/test_cli.py +++ b/test/e2e/test_cli.py @@ -87,7 +87,7 @@ def test_command_help(script_runner: ScriptRunner): def test_config_with_a_non_existent_file(script_runner: ScriptRunner, tmp_path: Path): config = tmp_path.joinpath('non-existent-config.json') ret = script_runner.run([ - 'bldr', '--config', str(config), 'build', 'ubuntu:bionic' + 'bldr', '--config', str(config), 'build', 'ubuntu:focal' ]) assert not ret.success assert "Unable to open configuration file: '{}'".format(config) in ret.stderr @@ -98,7 +98,7 @@ def test_config_with_a_unreadable_file(script_runner: ScriptRunner, tmp_path: Pa config.touch() config.chmod(0) ret = script_runner.run([ - 'bldr', '--config', str(config), 'build', 'ubuntu:bionic' + 'bldr', '--config', str(config), 'build', 'ubuntu:focal' ]) assert not ret.success assert "Unable to open configuration file: '{}'".format(config) in ret.stderr @@ -109,7 +109,7 @@ def test_config_with_a_non_json_file(script_runner: ScriptRunner, tmp_path: Path config.write_text('{"foo": ') ret = script_runner.run([ - 'bldr', '--config', str(config), 'build', 'ubuntu:bionic' + 'bldr', '--config', str(config), 'build', 'ubuntu:focal' ]) assert not ret.success assert "Unable to parse configuration file: 'Expecting value: line 1 column 9 (char 8)'" in ret.stderr diff --git a/test/integration/test_config.py b/test/integration/test_config.py index c9ac1d6..0d762e3 100644 --- a/test/integration/test_config.py +++ b/test/integration/test_config.py @@ -15,7 +15,7 @@ def test_config_with_an_existent_file(tmp_path: Path): config = tmp_path.joinpath('config.json') with config.open('w') as config_file: json.dump({'container_env': ['foo=bar', 'bar=baz']}, config_file) - bldr = CLI(['bldr', '--config', str(config), 'build', 'ubuntu:bionic']) + bldr = CLI(['bldr', '--config', str(config), 'build', 'ubuntu:focal']) assert bldr.args.container_env == [('foo', 'bar'), ('bar', 'baz')] @@ -23,9 +23,9 @@ def test_config_and_cmdline_arg_has_a_same_value_and_a_proper_type(tmp_path: Pat config = tmp_path.joinpath('config.json') with config.open('w') as config_file: json.dump({'hooks_dir': '/foo/hooks'}, config_file) - bldr = CLI(['bldr', '--config', str(config), 'build', 'ubuntu:bionic']) + bldr = CLI(['bldr', '--config', str(config), 'build', 'ubuntu:focal']) hooks_dir1 = bldr.args.hooks_dir - bldr = CLI(['bldr', 'build', 'ubuntu:bionic', '--hooks-dir', '/foo/hooks']) + bldr = CLI(['bldr', 'build', 'ubuntu:focal', '--hooks-dir', '/foo/hooks']) hooks_dir2 = bldr.args.hooks_dir assert hooks_dir1 == Path('/foo/hooks') @@ -37,7 +37,7 @@ def test_config_override_with_cmdline_arg(tmp_path): with config.open('w') as config_file: json.dump({'hooks_dir': '/foo/hooks'}, config_file) bldr = CLI( - ['bldr', 'build', 'ubuntu:bionic', '--hooks-dir', '/override/hooks'] + ['bldr', 'build', 'ubuntu:focal', '--hooks-dir', '/override/hooks'] ) assert bldr.args.hooks_dir == Path('/override/hooks') @@ -80,7 +80,7 @@ def test_container_env(tmp_path: Path, config_data, arguments, expected): with config.open('w') as config_file: json.dump(config_data, config_file) base_arguments = [ - 'bldr', '--config', str(config), 'build', 'ubuntu:bionic', + 'bldr', '--config', str(config), 'build', 'ubuntu:focal', ] bldr = CLI(base_arguments + arguments) diff --git a/test/unit/test_config.py b/test/unit/test_config.py index 3c6b2d2..503cf99 100644 --- a/test/unit/test_config.py +++ b/test/unit/test_config.py @@ -99,5 +99,5 @@ def test_argparse_inheritance(tmp_path: Path): def test_argparse_api_definitions(): parser = argparse.ArgumentParser() assert hasattr(parser, "_actions"), "parser object should have '_actions' attribute" - assert type(parser._actions) == list, "parser's '_actions' attribute must be a list" + assert isinstance(parser._actions, list), "parser's '_actions' attribute must be a list" assert hasattr(argparse, "_StoreAction"), "argparse must have a '_StoreAction' class defined" diff --git a/test/unit/test_resources.py b/test/unit/test_resources.py new file mode 100644 index 0000000..2e2ff3c --- /dev/null +++ b/test/unit/test_resources.py @@ -0,0 +1,5 @@ +import importlib_resources + + +def test_data_resource(): + assert importlib_resources.files('bldr').joinpath('data').is_dir()