kiwi-scp/kiwi_scp/instance.py

151 lines
4.3 KiB
Python
Raw Normal View History

2021-10-22 15:50:26 +00:00
import functools
import re
2021-12-02 01:38:29 +00:00
import subprocess
2021-10-22 15:50:26 +00:00
from pathlib import Path
2021-11-13 02:27:27 +00:00
from typing import Generator, List, Optional, Dict, Any
2021-10-22 15:50:26 +00:00
import attr
2021-10-28 13:53:32 +00:00
from ruamel.yaml.comments import CommentedMap
2021-10-22 15:50:26 +00:00
2021-11-13 02:27:27 +00:00
from ._constants import COMPOSE_FILE_NAME, CONF_DIRECTORY_NAME
from .config import KiwiConfig, ProjectConfig
2021-12-02 01:38:29 +00:00
from .executable import COMPOSE_EXE
2021-10-27 11:33:26 +00:00
from .misc import YAML
2021-10-22 15:50:26 +00:00
@attr.s
class Service:
name: str = attr.ib()
2021-11-03 15:32:01 +00:00
content: CommentedMap = attr.ib()
2021-12-02 01:38:29 +00:00
parent: Optional["Project"] = attr.ib(default=None)
_RE_CONFDIR = re.compile(r"^\s*\$(?:CONFDIR|{CONFDIR})/+(.*)$", flags=re.UNICODE)
2021-10-22 15:50:26 +00:00
2021-10-28 13:53:32 +00:00
@property
def configs(self) -> Generator[Path, None, None]:
2021-11-03 15:32:01 +00:00
if "volumes" not in self.content:
2021-10-28 13:53:32 +00:00
return
2021-10-22 15:50:26 +00:00
2021-11-03 15:32:01 +00:00
for volume in self.content["volumes"]:
2021-10-28 13:53:32 +00:00
host_part = volume.split(":")[0]
2021-12-02 01:38:29 +00:00
cd_match = Service._RE_CONFDIR.match(host_part)
2021-10-22 15:50:26 +00:00
2021-10-28 13:53:32 +00:00
if cd_match:
2021-10-28 14:48:54 +00:00
yield Path(cd_match.group(1))
2021-10-22 15:50:26 +00:00
2021-12-02 01:48:41 +00:00
def has_executable(self, exe_name: str) -> bool:
try:
# test if desired executable exists
COMPOSE_EXE.run(
["exec", "-T", self.name, "/bin/sh", "-c", f"command -v {exe_name}"],
check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
**self.parent.process_kwargs,
)
return True
except subprocess.CalledProcessError:
return False
2021-10-22 15:50:26 +00:00
2021-10-28 13:53:32 +00:00
@attr.s
class Services:
content: List[Service] = attr.ib()
def __str__(self) -> str:
return YAML().dump({
"services": {
2021-11-03 15:32:01 +00:00
service.name: service.content
2021-10-28 13:53:32 +00:00
for service in self.content
}
2021-10-28 14:48:54 +00:00
}).strip()
2021-11-06 02:45:27 +00:00
def __bool__(self) -> bool:
return bool(self.content)
2021-11-27 17:33:46 +00:00
@property
def names(self) -> Generator[str, None, None]:
return (
service.name
for service in self.content
)
2021-11-27 15:21:54 +00:00
def filter_existing(self, service_names: List[str]) -> "Services":
2021-11-06 02:45:27 +00:00
return Services([
service
for service in self.content
if service.name in service_names
])
2021-10-22 15:50:26 +00:00
@attr.s
2021-11-03 15:32:01 +00:00
class Project:
directory: Path = attr.ib()
2021-12-02 01:38:29 +00:00
parent: Optional["Instance"] = attr.ib(default=None)
2021-10-26 10:28:06 +00:00
2021-10-28 13:53:32 +00:00
@staticmethod
2021-10-26 13:59:38 +00:00
@functools.lru_cache(maxsize=10)
2021-11-03 15:32:01 +00:00
def _parse_compose_file(directory: Path) -> CommentedMap:
2021-10-26 13:59:38 +00:00
with open(directory.joinpath(COMPOSE_FILE_NAME), "r") as cf:
2021-10-27 11:33:26 +00:00
return YAML().load(cf)
2021-10-26 13:59:38 +00:00
2021-11-06 02:45:27 +00:00
@property
def name(self) -> str:
return self.directory.name
2021-11-13 02:27:27 +00:00
@property
2021-12-02 01:38:29 +00:00
def config(self) -> ProjectConfig:
return self.parent.config.get_project_config(self.name)
2021-11-13 02:27:27 +00:00
@property
def process_kwargs(self) -> Dict[str, Any]:
directory: Path = self.directory
project_name: str = self.name
2021-12-02 01:38:29 +00:00
kiwi_hub_name: str = self.parent.config.network.name
target_root_dir: Path = self.parent.config.storage.directory
2021-11-13 02:27:27 +00:00
conf_dir: Path = target_root_dir.joinpath(CONF_DIRECTORY_NAME)
target_dir: Path = target_root_dir.joinpath(project_name)
result: Dict[str, Any] = {
"cwd": str(directory),
"env": {
"COMPOSE_PROJECT_NAME": project_name,
"KIWI_HUB_NAME": kiwi_hub_name,
"TARGETROOT": str(target_root_dir),
"CONFDIR": str(conf_dir),
"TARGETDIR": str(target_dir),
},
}
2021-12-02 01:38:29 +00:00
result["env"].update(self.parent.config.environment)
2021-11-13 02:27:27 +00:00
return result
2021-11-06 02:45:27 +00:00
@property
def services(self) -> Services:
2021-11-03 15:32:01 +00:00
yml = Project._parse_compose_file(self.directory)
2021-11-06 02:45:27 +00:00
return Services([
2021-12-02 01:38:29 +00:00
Service(name, content, self)
for name, content in yml["services"].items()
2021-11-06 02:45:27 +00:00
])
2021-11-03 15:32:01 +00:00
2021-12-02 01:38:29 +00:00
@attr.s(frozen=True)
2021-11-03 15:32:01 +00:00
class Instance:
directory: Path = attr.ib(default=Path('.'))
@property
def config(self) -> KiwiConfig:
"""shorthand: get the current configuration"""
return KiwiConfig.from_directory(self.directory)
2021-11-13 02:27:27 +00:00
@functools.lru_cache(maxsize=None)
2021-12-02 01:38:29 +00:00
def get_project(self, project_name: str) -> Optional[Project]:
for project in self.config.projects:
2021-11-03 15:32:01 +00:00
if project.name == project_name:
2021-11-13 02:27:27 +00:00
return Project(
2021-12-02 01:38:29 +00:00
directory=self.directory.joinpath(project.name),
parent=self,
2021-11-13 02:27:27 +00:00
)