Source code for exasol.ansible.repository

import logging
from abc import abstractmethod
from collections.abc import Iterable
from pathlib import Path
from typing import Any

try:
    import importlib.resources as ir
except ImportError:  # pragma: no cover
    import importlib_resources as ir  # type: ignore[no-redef]


logger = logging.getLogger(__name__)


def _should_ignore(path: Any) -> bool:
    if path.name in {"__init__.py", "__pycache__", ".DS_Store"}:
        logger.debug("Ignoring %s for repository.", path)
        return True
    return False


class Asset:
    """
    Abstract representation of a copyable ansible asset within a repository.
    """

    def __init__(self, relative_path: Path):
        self.relative_path = relative_path

    @abstractmethod
    def copy_to(self, target_root: Path) -> None:
        """
        Copy this asset into the given target root.
        """
        ...

    @abstractmethod
    def paths(self) -> dict[Path, str]:
        """
        Return the contained paths for this asset.

        The string values in the dict are either "directory" or "file" to
        signal different types of assets.
        """
        ...


class ImportlibFileAsset(Asset):
    def __init__(self, src_file: Any, relative_path: Path):
        super().__init__(relative_path)
        self._src_file = src_file

    def copy_to(self, target_root: Path) -> None:
        target_file = target_root / self.relative_path
        target_file.parent.mkdir(parents=True, exist_ok=True)
        content = self._src_file.read_bytes()
        with open(target_file, "wb") as file:
            file.write(content)

    def paths(self) -> dict[Path, str]:
        return {self.relative_path: "file"}


class ImportlibDirectoryAsset(Asset):
    def __init__(self, src_path: Any, relative_path: Path):
        super().__init__(relative_path)
        self._src_path = src_path

    @classmethod
    def _paths(cls, src_path: Any, relative_path: Path) -> Iterable[tuple[Path, str]]:
        yield relative_path, "directory"
        for child in src_path.iterdir():
            if _should_ignore(child):
                continue
            child_path = relative_path / child.name
            if child.is_file():
                yield child_path, "file"
            else:
                yield from cls._paths(child, child_path)

    def copy_to(self, target_root: Path) -> None:
        for path, path_type in self._paths(self._src_path, self.relative_path):
            target = target_root / path
            if path_type == "file":
                target.parent.mkdir(parents=True, exist_ok=True)
                content = (
                    self._src_path / path.relative_to(self.relative_path)
                ).read_bytes()
                with open(target, "wb") as file:
                    file.write(content)
            else:
                target.mkdir(exist_ok=True)

    def paths(self) -> dict[Path, str]:
        return dict(self._paths(self._src_path, self.relative_path))


[docs] class Repository: """ Abstract source of top-level ansible assets. """ @abstractmethod def get_assets(self) -> Iterable[Asset]: """ Base class does not implement asset enumeration. This method is intentionally left empty because: - Different repository types (e.g. filesystem-based, package-based) expose different asset sources. - Subclasses like `ImportlibRepository` provide the actual implementation using their specific source (e.g. importlib resources). This class acts as an interface / abstraction layer. """ ...
[docs] class ImportlibRepository(Repository): """ Represents a repository containing ansible files (roles, playbooks, tasks, etc.). The repository is expected to be located within a Python module. Supports copy of the ansible files to a target folder. """ def __init__(self, package: Any): self._package = package def get_assets(self) -> Iterable[Asset]: """ Traverse the repository and yield all copyable assets below it. """ source_path = ir.files(self._package) for child in source_path.iterdir(): if _should_ignore(child): continue if child.is_file(): yield ImportlibFileAsset(child, Path(child.name)) else: yield ImportlibDirectoryAsset(child, Path(child.name))