diff --git a/kiwi_scp/_constants.py b/kiwi_scp/_constants.py index e5db944..843e901 100644 --- a/kiwi_scp/_constants.py +++ b/kiwi_scp/_constants.py @@ -1,9 +1,5 @@ import os -RESERVED_PROJECT_NAMES = [ - 'config' -] - ############# # REGEX PARTS @@ -40,6 +36,11 @@ CONF_DIRECTORY_NAME = 'config' # location for auxiliary Dockerfiles IMAGES_DIRECTORY_NAME = f"{KIWI_ROOT}/data/images" +# prohibited project names +RESERVED_PROJECT_NAMES = [ + CONFIG_DIRECTORY_NAME, +] + #################### # DOCKER IMAGE NAMES diff --git a/kiwi_scp/commands/cmd_update.py b/kiwi_scp/commands/cmd_update.py index 0d0e0af..630e8dc 100644 --- a/kiwi_scp/commands/cmd_update.py +++ b/kiwi_scp/commands/cmd_update.py @@ -53,15 +53,12 @@ class UpdateCommand(KiwiCommand): ): return - # services.copy_configs() - # return - ctx = get_current_context() assert isinstance(BuildCommand, click.Command) ctx.forward(BuildCommand) assert isinstance(PullCommand, click.Command) ctx.forward(PullCommand) - # TODO conf-copy + services.copy_configs() assert isinstance(DownCommand, click.Command) ctx.forward(DownCommand) assert isinstance(UpCommand, click.Command) diff --git a/kiwi_scp/instance.py b/kiwi_scp/instance.py index 6799280..3ce304b 100644 --- a/kiwi_scp/instance.py +++ b/kiwi_scp/instance.py @@ -3,7 +3,7 @@ from typing import Generator, Dict, Sequence import attr -from ._constants import KIWI_CONF_NAME +from ._constants import KIWI_CONF_NAME, CONFIG_DIRECTORY_NAME from .config import KiwiConfig from .project import Project @@ -22,6 +22,14 @@ class Instance: with open(self.directory.joinpath(KIWI_CONF_NAME), "w") as file: config.dump_kiwi_yml(file) + @property + def config_directory(self): + return self.directory.joinpath(CONFIG_DIRECTORY_NAME) + + @property + def storage_config_directory(self): + return self.config.storage.directory.joinpath(CONFIG_DIRECTORY_NAME) + @property def projects(self) -> Generator[Project, None, None]: for project in self.config.projects: diff --git a/kiwi_scp/project.py b/kiwi_scp/project.py index 5f7bca9..d816a7a 100644 --- a/kiwi_scp/project.py +++ b/kiwi_scp/project.py @@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, Optional, Dict, Any import attr from ruamel.yaml import CommentedMap -from ._constants import COMPOSE_FILE_NAME, CONF_DIRECTORY_NAME +from ._constants import COMPOSE_FILE_NAME, CONFIG_DIRECTORY_NAME from .config import ProjectConfig from .service import Service from .services import Services @@ -40,7 +40,7 @@ class Project: project_name: str = self.name kiwi_hub_name: str = self.parent_instance.config.network.name target_root_dir: Path = self.parent_instance.config.storage.directory - conf_dir: Path = target_root_dir.joinpath(CONF_DIRECTORY_NAME) + conf_dir: Path = target_root_dir.joinpath(CONFIG_DIRECTORY_NAME) target_dir: Path = target_root_dir.joinpath(project_name) result: Dict[str, Any] = { diff --git a/kiwi_scp/rootkit.py b/kiwi_scp/rootkit.py index c1a42da..112b078 100644 --- a/kiwi_scp/rootkit.py +++ b/kiwi_scp/rootkit.py @@ -2,7 +2,7 @@ import functools import logging import subprocess from pathlib import Path -from typing import Optional, TypeVar, Union, List +from typing import Optional, TypeVar, Union, Sequence, Any import attr @@ -11,18 +11,7 @@ from .executable import DOCKER_EXE _logger = logging.getLogger(__name__) -PSL = TypeVar("PSL", Union[Path, str], List[Union[Path, str]]) - - -def prefix_path(path: PSL, prefix: Path = Path("/mnt")) -> PSL: - if isinstance(path, Path): - return prefix.joinpath(path.absolute()) - - if isinstance(path, str): - return prefix_path(Path(path), prefix) - - elif isinstance(path, list): - return [prefix_path(prefix, p) for p in path] +ROOTKIT_PREFIX = Path("/mnt") @attr.s @@ -68,11 +57,38 @@ class Rootkit: ], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) def run(self, process_args, **kwargs) -> Optional[subprocess.CompletedProcess]: + any_sequence = TypeVar("any_sequence", Union[str, Path, Any], Sequence[Union[str, Path, Any]]) + + def parse_args(argument: any_sequence) -> any_sequence: + if isinstance(argument, str): + return argument + + elif isinstance(argument, Path): + if argument.is_absolute(): + argument = argument.relative_to("/") + + return str(ROOTKIT_PREFIX.joinpath(argument)) + + elif not isinstance(argument, Sequence): + return str(argument) + + else: + parsed = [parse_args(path) for path in argument] + + flat = [] + for item in parsed: + if not isinstance(item, list): + flat.append(item) + else: + flat.extend(item) + + return flat + self.__build_image() return DOCKER_EXE.run([ - 'run', '--rm', - '-v', '/:/mnt', - '-u', 'root', + "run", "--rm", + "-v", f"/:{ROOTKIT_PREFIX!s}", + "-u", "root", Rootkit.__image_name(self.image_tag), - *process_args + *parse_args(process_args) ], **kwargs) diff --git a/kiwi_scp/service.py b/kiwi_scp/service.py index 2631f34..c85b82f 100644 --- a/kiwi_scp/service.py +++ b/kiwi_scp/service.py @@ -25,10 +25,6 @@ class Service: _RE_CONFIGDIR = re.compile(r"^\s*\$(?:CONFIGDIR|{CONFIGDIR})/+(.*)$", flags=re.UNICODE) - @property - def parent_instance(self) -> "Instance": - return self.parent_project.parent_instance - @property def configs(self) -> Generator[Path, None, None]: if "volumes" not in self.content: diff --git a/kiwi_scp/services.py b/kiwi_scp/services.py index 7cacab5..f1b3455 100644 --- a/kiwi_scp/services.py +++ b/kiwi_scp/services.py @@ -1,12 +1,14 @@ +import subprocess from pathlib import Path -from typing import List, Generator, Optional, TYPE_CHECKING +from typing import List, Generator, Optional, TYPE_CHECKING, TypeVar, Union import attr +from .rootkit import Rootkit from .yaml import YAML if TYPE_CHECKING: - from .instance import Instance + from .project import Project from .service import Service @@ -19,33 +21,60 @@ class Services: "services": { service.name: service.content for service in self.content - } + }, + "configs": [ + str(config) + for config in self.configs + ], }).strip() def __bool__(self) -> bool: return bool(self.content) @property - def parent_instance(self) -> Optional["Instance"]: + def parent_project(self) -> Optional["Project"]: if not self: return - return self.content[0].parent_instance + return self.content[0].parent_project @property def configs(self) -> Generator[Path, None, None]: for service in self.content: yield from service.configs - # def copy_configs(self) -> None: - # instance = self.parent_instance - # - # if instance is None: - # return - # - # print(list(self.configs)) - # - # # Rootkit("rsync"). + def copy_configs(self) -> None: + path_str_list = TypeVar("path_str_list", Union[Path, str], List[Union[Path, str]]) + + def prefix_path(path: path_str_list, prefix: Path) -> path_str_list: + if isinstance(path, Path): + return prefix.absolute().joinpath(path) + + elif isinstance(path, str): + return prefix_path(Path(path), prefix) + + elif isinstance(path, list): + return [prefix_path(p, prefix) for p in path] + + project = self.parent_project + + if project is None: + return + + instance = project.parent_instance + cfgs = list(self.configs) + + local_cfgs = prefix_path(cfgs, instance.config_directory) + storage_cfgs = prefix_path(cfgs, instance.storage_config_directory) + storage_dirs = [path.parent for path in storage_cfgs] + + Rootkit("rsync").run([ + "mkdir", "-p", storage_dirs + ], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + + Rootkit("rsync").run([ + "rsync", "-rpt", list(zip(local_cfgs, storage_cfgs)) + ], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) @property def names(self) -> Generator[str, None, None]: