diff --git a/changelog/+gitrepo-shared-access.added.md b/changelog/+gitrepo-shared-access.added.md new file mode 100644 index 000000000..4725cc5b2 --- /dev/null +++ b/changelog/+gitrepo-shared-access.added.md @@ -0,0 +1 @@ +Add the `shared` option to `GitRepo` in `infrahub_sdk.testing.repository`. With `shared=True`, every user on the machine can read and write the Git repository that `GitRepo` creates, so Infrahub containers that run as another user than the test process can fetch from it and push to it. The default, `False`, keeps the current behavior. diff --git a/infrahub_sdk/testing/repository.py b/infrahub_sdk/testing/repository.py index 941f118ee..0bd67e711 100644 --- a/infrahub_sdk/testing/repository.py +++ b/infrahub_sdk/testing/repository.py @@ -1,6 +1,7 @@ from __future__ import annotations import asyncio +import os import shutil from dataclasses import dataclass, field from enum import Enum @@ -43,6 +44,8 @@ class GitRepo: directories_to_ignore: list[str] = field(default_factory=list) remote_directory_name: str = "/remote" _branches: list[str] = field(default_factory=list) + shared: bool = False + """Give every user read and write access to the repository, for Infrahub containers that run as another user.""" @property def repo(self) -> GitRepoManager: @@ -75,6 +78,33 @@ def init(self) -> None: porcelain.commit(repo=self._repo.git.path, message="First commit") porcelain.checkout(repo=self._repo.git, target=self.initial_branch) + if self.shared: + self._share() + + def _share(self) -> None: + """Give every user read and write access to the repository, now and to the files Git adds later. + + `core.sharedRepository` makes Git create the files of a later commit or push with the same access. + The files that exist already, including those copied from `src_directory`, get it here. + """ + config = self.repo.git.get_config() + config.set((b"core",), b"sharedRepository", b"0666") + config.write_to_path() + + root_directory = self.dst_directory / self.name + root_directory.chmod(0o777) + for root, directories, files in os.walk(root_directory): + for directory in directories: + directory_path = Path(root) / directory + if not directory_path.is_symlink(): + directory_path.chmod(0o777) + for file in files: + file_path = Path(root) / file + if file_path.is_symlink(): + continue + mode = file_path.stat().st_mode + file_path.chmod(mode | 0o666 | (0o111 if mode & 0o111 else 0)) + async def add_to_infrahub(self, client: InfrahubClient, branch: str | None = None) -> dict: input_data = { "data": { diff --git a/tests/unit/sdk/test_repository.py b/tests/unit/sdk/test_repository.py index a8364b1c9..81933237b 100644 --- a/tests/unit/sdk/test_repository.py +++ b/tests/unit/sdk/test_repository.py @@ -1,3 +1,4 @@ +import stat import tempfile from collections.abc import Generator from pathlib import Path @@ -92,3 +93,43 @@ def test_gitrepo_init(temp_dir: str) -> None: commit = repo._repo.git[repo._repo.git.head()] assert isinstance(commit, Commit) assert commit.message.decode("utf-8") == "First commit" + + +def _private_source(temp_dir: str) -> Path: + """A source directory that only its owner can access, as pytest's `tmp_path` creates them.""" + source = Path(temp_dir) / "source" + source.mkdir(mode=0o700) + (source / ".infrahub.yml").write_text("---\n", encoding="utf-8") + (source / "script.sh").write_text("#!/bin/sh\n", encoding="utf-8") + (source / "script.sh").chmod(0o700) + return source + + +def _permissions(path: Path) -> int: + return stat.S_IMODE(path.stat().st_mode) + + +def test_gitrepo_keeps_the_permissions_of_the_source_by_default(temp_dir: str) -> None: + destination = Path(temp_dir) / "remotes" + repo = GitRepo(name="private", src_directory=_private_source(temp_dir), dst_directory=destination) + + assert _permissions(destination / "private") == 0o700 + with pytest.raises(KeyError): + repo.repo.git.get_config().get((b"core",), b"sharedRepository") + + +def test_gitrepo_shared_gives_every_user_read_and_write_access(temp_dir: str) -> None: + destination = Path(temp_dir) / "remotes" + repo = GitRepo(name="shared", src_directory=_private_source(temp_dir), dst_directory=destination, shared=True) + root = destination / "shared" + + assert repo.repo.git.get_config().get((b"core",), b"sharedRepository") == b"0666" + for path in [root, *root.rglob("*")]: + if path.is_symlink(): + continue + if path.is_dir(): + assert _permissions(path) == 0o777, path + else: + assert _permissions(path) & 0o666 == 0o666, path + assert _permissions(root / "script.sh") == 0o777 + assert _permissions(root / ".infrahub.yml") == 0o666