diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f727abc..084de84 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -23,7 +23,7 @@ jobs: python-version: 3.x - name: Install dependencies run: | - pip install --user strato-skipper + pip install --user . mkdir -p ~/.docker echo "{}" > ~/.docker/config.json touch ${HOME}/.gitconfig diff --git a/pyproject.toml b/pyproject.toml index 73cfc74..ea1da36 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -34,7 +34,9 @@ dependencies = [ "requests-bearer==0.5.1", "retry", "setuptools", - "pbr" + "pbr", + "importlib_metadata; python_version < '3.8'", + "importlib_resources; python_version < '3.9'" ] [project.optional-dependencies] diff --git a/skipper/cli.py b/skipper/cli.py index 0fe4fe0..d85524f 100644 --- a/skipper/cli.py +++ b/skipper/cli.py @@ -10,7 +10,10 @@ import six import tabulate from pbr import packaging -from pkg_resources import get_distribution +try: + from importlib.metadata import version as get_distribution +except ImportError: + from importlib_metadata import version as get_distribution from skipper import builder, git, runner, utils from skipper.builder import BuildOptions, Image @@ -91,6 +94,7 @@ def cli( ctx.obj["env"] = ctx.default_map.get("env", {}) ctx.obj["containers"] = ctx.default_map.get("containers") ctx.obj["volumes"] = ctx.default_map.get("volumes") + ctx.obj["entrypoint"] = ctx.default_map.get("entrypoint") ctx.obj["workdir"] = ctx.default_map.get("workdir") ctx.obj["workspace"] = ctx.default_map.get("workspace", None) ctx.obj["container_context"] = ctx.default_map.get("container_context") @@ -269,6 +273,7 @@ def run(ctx, interactive, name, env, publish, cache, command): net=ctx.obj["build_container_net"], publish=publish, volumes=ctx.obj.get("volumes"), + entrypoint=ctx.obj.get("entrypoint"), workdir=ctx.obj.get("workdir"), use_cache=cache, workspace=ctx.obj.get("workspace"), @@ -310,6 +315,7 @@ def make(ctx, interactive, name, env, makefile, cache, publish, make_params): net=ctx.obj["build_container_net"], publish=publish, volumes=ctx.obj.get("volumes"), + entrypoint=ctx.obj.get("entrypoint"), workdir=ctx.obj.get("workdir"), use_cache=cache, workspace=ctx.obj.get("workspace"), @@ -347,6 +353,7 @@ def shell(ctx, env, name, cache, publish): net=ctx.obj["build_container_net"], publish=publish, volumes=ctx.obj.get("volumes"), + entrypoint=ctx.obj.get("entrypoint"), workdir=ctx.obj.get("workdir"), use_cache=cache, workspace=ctx.obj.get("workspace"), @@ -360,7 +367,7 @@ def version(): output skipper version """ utils.logger.debug("printing skipper version") - click.echo(get_distribution("strato-skipper").version) # pylint: disable=no-member + click.echo(get_distribution("strato-skipper")) # pylint: disable=no-member @cli.command() diff --git a/skipper/runner.py b/skipper/runner.py index 9dca49d..a60a606 100644 --- a/skipper/runner.py +++ b/skipper/runner.py @@ -21,14 +21,14 @@ def get_default_net(): # pylint: disable=too-many-arguments # pylint: disable=too-many-positional-arguments def run(command, fqdn_image=None, environment=None, interactive=False, name=None, net=None, publish=(), volumes=None, - workdir=None, use_cache=False, workspace=None, env_file=(), stdout_to_stderr=False): + entrypoint=None, workdir=None, use_cache=False, workspace=None, env_file=(), stdout_to_stderr=False): if not net: net = get_default_net() if fqdn_image is not None: return _run_nested(fqdn_image, environment, command, interactive, name, net, publish, volumes, - workdir, use_cache, workspace, env_file) + entrypoint, workdir, use_cache, workspace, env_file) return _run(command, stdout_to_stderr=stdout_to_stderr) @@ -50,7 +50,7 @@ def _run(cmd_args, stdout_to_stderr=False): # pylint: disable=too-many-branches # pylint: disable=too-many-arguments # pylint: disable=too-many-positional-arguments -def _run_nested(fqdn_image, environment, command, interactive, name, net, publish, volumes, workdir, use_cache, workspace, env_file): +def _run_nested(fqdn_image, environment, command, interactive, name, net, publish, volumes, entrypoint, workdir, use_cache, workspace, env_file): cwd = os.getcwd() if workspace is None: workspace = os.path.dirname(cwd) @@ -114,7 +114,7 @@ def _run_nested(fqdn_image, environment, command, interactive, name, net, publis cmd = handle_workdir(cmd, cwd, workdir) - cmd += ['--entrypoint', '/opt/skipper/skipper-entrypoint.sh'] + cmd += ['--entrypoint', entrypoint or '/opt/skipper/skipper-entrypoint.sh'] cmd += [fqdn_image] cmd += [' '.join(command)] diff --git a/skipper/utils.py b/skipper/utils.py index 45d1407..60b17ed 100644 --- a/skipper/utils.py +++ b/skipper/utils.py @@ -9,7 +9,10 @@ import requests from requests_bearer import HttpBearerAuth import urllib3 -import pkg_resources +try: + from importlib.resources import files +except ImportError: + from importlib_resources import files REGISTRY_BASE_URL = 'https://%(registry)s/v2/' @@ -178,7 +181,7 @@ def get_runtime_command(): def get_extra_file(filename): - return pkg_resources.resource_filename("skipper", f"data/{filename}") + return str(files("skipper").joinpath(f"data/{filename}")) def run_container_command(args): diff --git a/tests/test_cli.py b/tests/test_cli.py index 85bf0fa..4f0b4ba 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -132,6 +132,16 @@ "workspace": "/test/workspace", } +SKIPPER_CONF_WITH_ENTRYPOINT = { + "registry": REGISTRY, + "build_container_image": SKIPPER_CONF_BUILD_CONTAINER_IMAGE, + "build_container_tag": SKIPPER_CONF_BUILD_CONTAINER_TAG, + "make": { + "makefile": SKIPPER_CONF_MAKEFILE, + }, + "entrypoint": "/custom/entrypoint.sh", +} + SKIPPER_CONF_WITH_GIT_REV = { "registry": REGISTRY, "build_container_image": SKIPPER_CONF_BUILD_CONTAINER_IMAGE, @@ -332,6 +342,7 @@ def test_make_without_build_container_tag_with_context(self, skipper_runner_run_ net=None, publish=(), volumes=None, + entrypoint=None, workdir=None, use_cache=False, workspace=None, @@ -1104,6 +1115,7 @@ def test_run_with_existing_local_build_container(self, skipper_runner_run_mock): net=None, publish=(), volumes=None, + entrypoint=None, workdir=None, workspace=None, use_cache=False, @@ -1136,6 +1148,7 @@ def test_run_with_existing_remote_build_container(self, skipper_runner_run_mock, net=None, publish=(), volumes=None, + entrypoint=None, workdir=None, workspace=None, use_cache=False, @@ -1174,6 +1187,7 @@ def test_run_with_defaults_from_config_file(self, skipper_runner_run_mock): net=None, publish=(), volumes=None, + entrypoint=None, workdir=None, workspace=None, use_cache=False, @@ -1197,6 +1211,7 @@ def test_run_with_defaults_and_env_from_env_file(self, skipper_runner_run_mock): net=None, publish=(), volumes=None, + entrypoint=None, workdir=None, workspace=None, use_cache=False, @@ -1220,6 +1235,7 @@ def test_run_with_defaults_and_env_from_multiple_env_file(self, skipper_runner_r net=None, publish=(), volumes=None, + entrypoint=None, workdir=None, workspace=None, use_cache=False, @@ -1244,6 +1260,7 @@ def test_run_with_env_overriding_config_file(self, skipper_runner_run_mock): net=None, publish=(), volumes=None, + entrypoint=None, workdir=None, workspace=None, use_cache=False, @@ -1272,6 +1289,7 @@ def test_run_with_env_list(self, skipper_runner_run_mock): net=None, publish=(), volumes=None, + entrypoint=None, workdir=None, workspace=None, use_cache=False, @@ -1300,6 +1318,7 @@ def test_run_with_env_list_get_from_env(self, skipper_runner_run_mock): net=None, publish=(), volumes=None, + entrypoint=None, workdir=None, workspace=None, use_cache=False, @@ -1335,6 +1354,7 @@ def test_run_with_env(self, skipper_runner_run_mock): net=None, publish=(), volumes=None, + entrypoint=None, workdir=None, workspace=None, use_cache=False, @@ -1358,6 +1378,7 @@ def test_run_interactive_from_environment(self, skipper_runner_run_mock): net=None, publish=(), volumes=None, + entrypoint=None, workdir=None, workspace=None, use_cache=False, @@ -1381,6 +1402,7 @@ def test_run_non_interactive_from_environment(self, skipper_runner_run_mock): net=None, publish=(), volumes=None, + entrypoint=None, workdir=None, workspace=None, use_cache=False, @@ -1403,6 +1425,7 @@ def test_run_non_interactive(self, skipper_runner_run_mock): net=None, publish=(), volumes=None, + entrypoint=None, workdir=None, workspace=None, use_cache=False, @@ -1441,6 +1464,7 @@ def test_run_without_build_container_tag(self, skipper_runner_run_mock): net=None, publish=(), volumes=None, + entrypoint=None, workdir=None, workspace=None, use_cache=False, @@ -1469,6 +1493,7 @@ def test_run_without_build_container_tag_cached(self, skipper_runner_run_mock): net=None, publish=(), volumes=None, + entrypoint=None, workdir=None, workspace=None, use_cache=True, @@ -1495,6 +1520,7 @@ def test_run_with_non_default_net(self, skipper_runner_run_mock): net="non-default-net", publish=(), volumes=None, + entrypoint=None, workdir=None, workspace=None, use_cache=False, @@ -1525,6 +1551,7 @@ def test_run_with_publish_single_port(self, skipper_runner_run_mock): net="non-default-net", publish=("123:123",), volumes=None, + entrypoint=None, workdir=None, workspace=None, use_cache=False, @@ -1555,6 +1582,7 @@ def test_run_with_publish_multiple_ports(self, skipper_runner_run_mock): net="non-default-net", publish=("123:123", "12:12"), volumes=None, + entrypoint=None, workdir=None, workspace=None, use_cache=False, @@ -1585,6 +1613,7 @@ def test_run_with_publish_port_range(self, skipper_runner_run_mock): net="non-default-net", publish=("123:123", "12-13:12-13"), volumes=None, + entrypoint=None, workdir=None, workspace=None, use_cache=False, @@ -1678,6 +1707,7 @@ def test_run_with_defaults_from_config_file_including_volumes(self, skipper_runn publish=(), volumes=["volume1", "volume2"], workspace=None, + entrypoint=None, workdir=None, use_cache=False, env_file=(), @@ -1701,6 +1731,7 @@ def test_run_with_defaults_from_config_file_including_workdir(self, skipper_runn net=None, publish=(), volumes=None, + entrypoint=None, workdir="test-workdir", workspace=None, use_cache=False, @@ -1725,6 +1756,7 @@ def test_run_with_defaults_from_config_file_including_workspace(self, skipper_ru net=None, publish=(), volumes=None, + entrypoint=None, workdir=None, workspace="/test/workspace", use_cache=False, @@ -1750,6 +1782,7 @@ def test_run_with_config_including_git_revision_with_uncommitted_changes(self, s net=None, publish=(), volumes=None, + entrypoint=None, workdir=None, use_cache=False, workspace=None, @@ -1775,6 +1808,7 @@ def test_run_with_config_including_git_revision_without_uncommitted_changes(self net=None, publish=(), volumes=None, + entrypoint=None, workdir=None, workspace=None, use_cache=False, @@ -1799,6 +1833,7 @@ def test_make(self, skipper_runner_run_mock): net=None, publish=(), volumes=None, + entrypoint=None, workdir=None, workspace=None, use_cache=False, @@ -1823,6 +1858,7 @@ def test_make_with_default_params(self, skipper_runner_run_mock): net=None, publish=(), volumes=None, + entrypoint=None, workdir=None, workspace=None, use_cache=False, @@ -1847,6 +1883,7 @@ def test_make_with_additional_make_params(self, skipper_runner_run_mock): net=None, publish=(), volumes=None, + entrypoint=None, workdir=None, workspace=None, use_cache=False, @@ -1873,6 +1910,7 @@ def test_make_with_defaults_from_config_file(self, skipper_runner_run_mock): net=None, publish=(), volumes=None, + entrypoint=None, workdir=None, workspace=None, use_cache=False, @@ -1912,6 +1950,7 @@ def test_make_without_build_container_tag(self, skipper_runner_run_mock): net=None, publish=(), volumes=None, + entrypoint=None, workdir=None, workspace=None, use_cache=False, @@ -1937,6 +1976,7 @@ def test_shell(self, skipper_runner_run_mock): net=None, publish=(), volumes=None, + entrypoint=None, workdir=None, workspace=None, use_cache=False, @@ -1947,14 +1987,44 @@ def test_shell(self, skipper_runner_run_mock): @mock.patch("skipper.cli.get_distribution", autospec=True) def test_version(self, get_dist_mock, echo_mock): expected_version = "1.2.3" - get_dist_mock.return_value = mock.MagicMock() - get_dist_mock.return_value.version = expected_version + get_dist_mock.return_value = expected_version self._invoke_cli( subcmd="version", ) echo_mock.assert_called_once_with(expected_version) + @mock.patch("os.path.exists", mock.MagicMock(autospec=True, return_value=True)) + @mock.patch("skipper.config.load_defaults", mock.MagicMock(autospec=True, return_value=SKIPPER_CONF_WITH_ENTRYPOINT)) + @mock.patch("subprocess.check_output", mock.MagicMock(autospec=True, return_value="1234567\n")) + @mock.patch("skipper.runner.run", autospec=True) + def test_run_with_defaults_from_config_file_including_entrypoint(self, skipper_runner_run_mock): + command = ["ls", "-l"] + run_params = command + self._invoke_cli(defaults=config.load_defaults(), subcmd="run", subcmd_params=run_params) + expected_fqdn_image = "skipper-conf-build-container-image:skipper-conf-build-container-tag" + skipper_runner_run_mock.assert_called_once_with( + command, + fqdn_image=expected_fqdn_image, + environment=[], + interactive=False, + name=None, + net=None, + publish=(), + volumes=None, + entrypoint="/custom/entrypoint.sh", + workdir=None, + workspace=None, + use_cache=False, + env_file=(), + ) + + @mock.patch("builtins.open", mock.mock_open(read_data="completion-script-content")) + @mock.patch("skipper.utils.get_extra_file", mock.MagicMock(autospec=True, return_value="/path/to/complete.sh")) + def test_completion(self): + result = self._invoke_cli(global_params=None, subcmd="completion") + self.assertEqual(result.exit_code, 0) + def _invoke_cli(self, defaults=None, global_params=None, subcmd=None, subcmd_params=None): self.assertFalse(subcmd is None and subcmd_params is not None, "No sub-command was provided!") diff --git a/tests/test_runner.py b/tests/test_runner.py index 707d101..fc22460 100644 --- a/tests/test_runner.py +++ b/tests/test_runner.py @@ -75,7 +75,7 @@ def test_run_complex_command_not_nested(self, popen_mock): @mock.patch("grp.getgrnam", autospec=True) @mock.patch("subprocess.Popen", autospec=False) @mock.patch("subprocess.check_output", autospec=False) - @mock.patch("pkg_resources.resource_filename", autospec=False) + @mock.patch("skipper.utils.get_extra_file", autospec=False) def test_run_simple_command_nested_network_exist( self, resource_filename_mock, check_output_mock, popen_mock, grp_getgrnam_mock, os_getuid_mock ): @@ -145,7 +145,7 @@ def test_run_simple_command_nested_network_exist( @mock.patch("grp.getgrnam", autospec=True) @mock.patch("subprocess.Popen", autospec=False) @mock.patch("subprocess.check_output", autospec=False) - @mock.patch("pkg_resources.resource_filename", autospec=False) + @mock.patch("skipper.utils.get_extra_file", autospec=False) def test_run_simple_command_nested_network_not_exist( self, resource_filename_mock, check_output_mock, popen_mock, grp_getgrnam_mock, os_getuid_mock ): @@ -215,7 +215,7 @@ def test_run_simple_command_nested_network_not_exist( @mock.patch("grp.getgrnam", autospec=True) @mock.patch("subprocess.Popen", autospec=False) @mock.patch("subprocess.check_output", autospec=False) - @mock.patch("pkg_resources.resource_filename", autospec=False) + @mock.patch("skipper.utils.get_extra_file", autospec=False) def test_run_simple_command_nested_with_env( self, resource_filename_mock, check_output_mock, popen_mock, grp_getgrnam_mock, os_getuid_mock ): @@ -289,7 +289,7 @@ def test_run_simple_command_nested_with_env( @mock.patch("grp.getgrnam", autospec=True) @mock.patch("subprocess.Popen", autospec=False) @mock.patch("subprocess.check_output", autospec=False) - @mock.patch("pkg_resources.resource_filename", autospec=False) + @mock.patch("skipper.utils.get_extra_file", autospec=False) def test_run_simple_command_nested_with_env_file( self, resource_filename_mock, check_output_mock, popen_mock, grp_getgrnam_mock, os_getuid_mock ): @@ -361,7 +361,7 @@ def test_run_simple_command_nested_with_env_file( @mock.patch("grp.getgrnam", autospec=True) @mock.patch("subprocess.Popen", autospec=False) @mock.patch("subprocess.check_output", autospec=False) - @mock.patch("pkg_resources.resource_filename", autospec=False) + @mock.patch("skipper.utils.get_extra_file", autospec=False) def test_run_simple_command_nested_with_multiple_env_files( self, resource_filename_mock, check_output_mock, popen_mock, grp_getgrnam_mock, os_getuid_mock ): @@ -435,7 +435,7 @@ def test_run_simple_command_nested_with_multiple_env_files( @mock.patch("grp.getgrnam", autospec=True) @mock.patch("subprocess.Popen", autospec=False) @mock.patch("subprocess.check_output", autospec=False) - @mock.patch("pkg_resources.resource_filename", autospec=False) + @mock.patch("skipper.utils.get_extra_file", autospec=False) def test_run_simple_command_nested_interactive( self, resource_filename_mock, check_output_mock, popen_mock, grp_getgrnam_mock, os_getuid_mock ): @@ -512,7 +512,7 @@ def test_run_simple_command_nested_interactive( ) @mock.patch("subprocess.Popen", autospec=False) @mock.patch("subprocess.check_output", autospec=False) - @mock.patch("pkg_resources.resource_filename", autospec=False) + @mock.patch("skipper.utils.get_extra_file", autospec=False) def test_run_complex_command_nested( self, resource_filename_mock, check_output_mock, popen_mock, grp_getgrnam_mock, os_getuid_mock ): @@ -582,7 +582,7 @@ def test_run_complex_command_nested( @mock.patch("grp.getgrnam", autospec=True) @mock.patch("subprocess.Popen", autospec=False) @mock.patch("subprocess.check_output", autospec=False) - @mock.patch("pkg_resources.resource_filename", autospec=False) + @mock.patch("skipper.utils.get_extra_file", autospec=False) def test_run_complex_command_nested_with_env( self, resource_filename_mock, check_output_mock, popen_mock, grp_getgrnam_mock, os_getuid_mock ): @@ -659,7 +659,7 @@ def test_run_complex_command_nested_with_env( @mock.patch("grp.getgrnam", autospec=True) @mock.patch("subprocess.Popen", autospec=False) @mock.patch("subprocess.check_output", autospec=False) - @mock.patch("pkg_resources.resource_filename", autospec=False) + @mock.patch("skipper.utils.get_extra_file", autospec=False) def test_run_complex_command_nested_with_special_case_verification( self, resource_filename_mock, diff --git a/tests/test_runner_podman.py b/tests/test_runner_podman.py index e93e0cc..e0c7c4b 100644 --- a/tests/test_runner_podman.py +++ b/tests/test_runner_podman.py @@ -62,7 +62,7 @@ def test_run_complex_command_not_nested(self, popen_mock): @mock.patch("os.getuid", autospec=True) @mock.patch("subprocess.Popen", autospec=False) @mock.patch("subprocess.check_output", autospec=False) - @mock.patch("pkg_resources.resource_filename", autospec=False) + @mock.patch("skipper.utils.get_extra_file", autospec=False) def test_run_simple_command_nested_network_exist( self, resource_filename_mock, check_output_mock, popen_mock, os_getuid_mock ): @@ -125,7 +125,7 @@ def test_run_simple_command_nested_network_exist( @mock.patch("os.getuid", autospec=True) @mock.patch("subprocess.Popen", autospec=False) @mock.patch("subprocess.check_output", autospec=False) - @mock.patch("pkg_resources.resource_filename", autospec=True) + @mock.patch("skipper.utils.get_extra_file", autospec=True) def test_run_simple_command_nested_network_not_exist( self, resource_filename_mock, check_output_mock, popen_mock, os_getuid_mock ): @@ -188,7 +188,7 @@ def test_run_simple_command_nested_network_not_exist( @mock.patch("os.getuid", autospec=True) @mock.patch("subprocess.Popen", autospec=False) @mock.patch("subprocess.check_output", autospec=False) - @mock.patch("pkg_resources.resource_filename", autospec=False) + @mock.patch("skipper.utils.get_extra_file", autospec=False) def test_run_complex_command_nested(self, resource_filename_mock, check_output_mock, popen_mock, os_getuid_mock): resource_filename_mock.return_value = "entrypoint.sh" popen_mock.return_value.stdout.readline.side_effect = ["aaa", "bbb", "ccc", ""] @@ -249,7 +249,7 @@ def test_run_complex_command_nested(self, resource_filename_mock, check_output_m @mock.patch("os.getuid", autospec=True) @mock.patch("subprocess.Popen", autospec=False) @mock.patch("subprocess.check_output", autospec=False) - @mock.patch("pkg_resources.resource_filename", autospec=False) + @mock.patch("skipper.utils.get_extra_file", autospec=False) @mock.patch("os.makedirs", mock.MagicMock(autospec=True, side_effect=mock_makedirs)) def test_run_non_existent_unauthorized_volume( self, resource_filename_mock, check_output_mock, popen_mock, os_getuid_mock @@ -315,7 +315,7 @@ def test_run_non_existent_unauthorized_volume( @mock.patch("os.getuid", autospec=True) @mock.patch("subprocess.Popen", autospec=False) @mock.patch("subprocess.check_output", autospec=False) - @mock.patch("pkg_resources.resource_filename", autospec=False) + @mock.patch("skipper.utils.get_extra_file", autospec=False) def test_run_complex_command_nested_with_env( self, resource_filename_mock, check_output_mock, popen_mock, os_getuid_mock ):