From ac52a96feca72f89c196a0b0ce8f2c9814b504ea Mon Sep 17 00:00:00 2001 From: Muhammad Faraz Maqsood Date: Fri, 22 Nov 2024 12:23:15 +0500 Subject: [PATCH] chore: add is_docker_rootless() in tutor-discovery - move is_docker_rootless method from tutor to tutor-discovery - move is_docker_rootless related tests from tutor to tutor-discovery and modify makefile according to it. --- Makefile | 7 +++-- .../20241122_121506_faraz.maqsood_sumac.md | 1 + tests/__init__.py | 0 tests/test_utils.py | 26 +++++++++++++++++++ tutordiscovery/plugin.py | 11 +++++--- tutordiscovery/utils.py | 16 ++++++++++++ 6 files changed, 56 insertions(+), 5 deletions(-) create mode 100644 changelog.d/20241122_121506_faraz.maqsood_sumac.md create mode 100644 tests/__init__.py create mode 100644 tests/test_utils.py create mode 100644 tutordiscovery/utils.py diff --git a/Makefile b/Makefile index 2adc5ab..5bda776 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,10 @@ .DEFAULT_GOAL := help .PHONY: docs -SRC_DIRS = ./tutordiscovery +SRC_DIRS = ./tutordiscovery ./tests BLACK_OPTS = --exclude templates ${SRC_DIRS} # Warning: These checks are run on every PR. -test: test-lint test-types test-format # Run some static checks. +test: test-lint test-types test-format test-unit # Run some static checks. test-format: ## Run code formatting tests. black --check --diff $(BLACK_OPTS) @@ -15,6 +15,9 @@ test-lint: ## Run code linting tests test-types: ## Run type checks. mypy --exclude=templates --ignore-missing-imports --implicit-reexport --strict ${SRC_DIRS} +test-unit: ## Run unit tests + python -m unittest discover tests + format: ## Format code automatically. black $(BLACK_OPTS) diff --git a/changelog.d/20241122_121506_faraz.maqsood_sumac.md b/changelog.d/20241122_121506_faraz.maqsood_sumac.md new file mode 100644 index 0000000..3c77ef5 --- /dev/null +++ b/changelog.d/20241122_121506_faraz.maqsood_sumac.md @@ -0,0 +1 @@ +- [Improvement] Move is_docker_rootless method related to elasticsearch from tutor core to tutor-discovery. (by @Faraz32123) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_utils.py b/tests/test_utils.py new file mode 100644 index 0000000..0a2f766 --- /dev/null +++ b/tests/test_utils.py @@ -0,0 +1,26 @@ +import subprocess +import unittest +from unittest.mock import MagicMock, patch + +from tutordiscovery import utils + + +class UtilsTests(unittest.TestCase): + @patch("subprocess.run") + def test_is_docker_rootless(self, mock_run: MagicMock) -> None: + # Mock rootless `docker info` output + utils.is_docker_rootless.cache_clear() + mock_run.return_value.stdout = "some prefix\n rootless foo bar".encode("utf-8") + self.assertTrue(utils.is_docker_rootless()) + + # Mock regular `docker info` output + utils.is_docker_rootless.cache_clear() + mock_run.return_value.stdout = "some prefix, regular docker".encode("utf-8") + self.assertFalse(utils.is_docker_rootless()) + + @patch("subprocess.run") + def test_is_docker_rootless_podman(self, mock_run: MagicMock) -> None: + """Test the `is_docker_rootless` when podman is used or any other error with `docker info`""" + utils.is_docker_rootless.cache_clear() + mock_run.side_effect = subprocess.CalledProcessError(1, "docker info") + self.assertFalse(utils.is_docker_rootless()) diff --git a/tutordiscovery/plugin.py b/tutordiscovery/plugin.py index f49af0b..5522a33 100644 --- a/tutordiscovery/plugin.py +++ b/tutordiscovery/plugin.py @@ -9,6 +9,7 @@ from tutor.__about__ import __version_suffix__ from .__about__ import __version__ +from .utils import is_docker_rootless # Handle version suffix in nightly mode, just like tutor core if __version_suffix__: @@ -95,7 +96,7 @@ # Automount /openedx/discovery folder from the container -@tutor_hooks.Filters.COMPOSE_MOUNTS.add() +@tutor_hooks.Filters.COMPOSE_MOUNTS.add() # type: ignore def _mount_course_discovery( mounts: list[tuple[str, str]], name: str ) -> list[tuple[str, str]]: @@ -105,7 +106,7 @@ def _mount_course_discovery( # Bind-mount repo at build-time, both for prod and dev images -@tutor_hooks.Filters.IMAGES_BUILD_MOUNTS.add() +@tutor_hooks.Filters.IMAGES_BUILD_MOUNTS.add() # type: ignore def _mount_course_discovery_on_build( mounts: list[tuple[str, str]], host_path: str ) -> list[tuple[str, str]]: @@ -127,6 +128,10 @@ def _mount_course_discovery_on_build( ("discovery/apps", "plugins"), ], ) +# Template variables +tutor_hooks.Filters.ENV_TEMPLATE_VARIABLES.add_item( + ("is_docker_rootless", is_docker_rootless), +) # Load patches from files for path in glob( os.path.join( @@ -150,7 +155,7 @@ def _mount_course_discovery_on_build( ) -@tutor_hooks.Filters.APP_PUBLIC_HOSTS.add() +@tutor_hooks.Filters.APP_PUBLIC_HOSTS.add() # type: ignore def _print_discovery_public_hosts( hosts: list[str], context_name: t.Literal["local", "dev"] ) -> list[str]: diff --git a/tutordiscovery/utils.py b/tutordiscovery/utils.py new file mode 100644 index 0000000..cfab7f3 --- /dev/null +++ b/tutordiscovery/utils.py @@ -0,0 +1,16 @@ +import subprocess +from functools import lru_cache + + +@lru_cache(maxsize=None) +def is_docker_rootless() -> bool: + """ + A helper function to determine if Docker is running in rootless mode. + + - https://docs.docker.com/engine/security/rootless/ + """ + try: + results = subprocess.run(["docker", "info"], capture_output=True, check=True) + return "rootless" in results.stdout.decode() + except subprocess.CalledProcessError: + return False