diff --git a/servs.py b/servs.py new file mode 100644 index 0000000..8533269 --- /dev/null +++ b/servs.py @@ -0,0 +1,173 @@ +from typing import TypedDict as Dict +from typing import Self, Literal, Never +from collections.abc import Sequence +from custtypes import Roles, Scopes, PathCollection, PathRoles, Software, SoftwareRoles, ExecutedPath, PacMans, UserName, GroupName, IdlePath, ExecutedPath +from pathlib import Path, PurePath +from sshkey import SSHKeyCollection, SSHKeyType, SSHKey +from whereami import PROJ_ROLES + +class App: + def __init__(self, name: Software, role: SoftwareRoles = SoftwareRoles.client, paths: PathRoles | None = None): + self.__name = name + # @TODO create dict type hint for below data struct + self.alt_names = dict() + self.alt_names[PacMans.APT] = self.__name + self.role = role + if paths is not None: + if Roles.EXE in paths: + setattr(self, "_" + Roles.EXE.name.lower(), paths[Roles.EXE.name.lower()]) + if Roles.CONF in paths: + setattr(self, "_" + Roles.CONF.name.lower(), paths[Roles.CONF.name.lower()]) + if Roles.DATA in paths: + setattr(self, "_" + Roles.DATA.name.lower(), paths[Roles.DATA.name.lower()]) + self.__parents: tuple[Self] | None = None + self.__children: tuple[Self| None] = [] + self.__api: str | None = None + self.__current_filepath: IdlePath | ExecutedPath | None = None + self.__content: str | None = None + + @property + def conf_paths(self): + if hasattr(self, "_" + Roles.CONF.name.lower()): + return self._conf + else: + raise Exception + + @property + def data_paths(self): + if hasattr(self, "_" + Roles.DATA.name.lower()): + return self._data + else: + raise Exception + + @property + def exec_paths(self): + if hasattr(self, "_" + Roles.EXE.name.lower()): + return self._exe + else: + raise Exception + + def append(self, datatype: Roles = Roles.CONF, scope: Scopes | None = None, path: IdlePath | ExecutedPath | str | PathCollection | None = None): + if path is None: + raise TypeError + + datatype = datatype.name.lower() + + if hasattr(self, "_" + datatype): + paths = getattr(self, "_" + datatype) + else: + setattr(self, "_" + datatype, dict()) + + if scope is not None: + if isinstance(path, str): + path: ExecutedPath = Path(path) + + if scope.name.lower() not in paths: + paths[scope.name.lower()] = [] + + paths[scope.name.lower()].append(path) + else: + paths: PathCollection = path + + setattr(self, "_" + datatype, paths) + + def inherit(self, other): + if other._App__children is None: + other._App__children = tuple() + + if self not in other._App__children: + other.adopt(self) + + self.__parents = (*self.__parents, other) + + def adopt(self, other: Self): + if other._App__parents is None: + other._App__parents = tuple() + + if self not in other._App__parent: + other.inherit(self) + + self.__children = (*self.__children, other) + + def __enter__(self) -> dict | Sequence: + self.__content = self.__current_filepath.read_text() + return self.__content + + def __exit__(self, exc_type, exc_value, exc_traceback) -> None: + # txt = yams.dump(self.__content) + # self.__file.write(txt) + # del txt + self.__file.close() + + def __call__(self, path = str, mode = "r+", scope: Scopes = Scopes.PROJ, index: int = 0) -> None: + if not hasattr(self, "_" + Roles.CONF.name.lower()): + raise Exception + + conf_coll = self._conf[scope.name.lower()] + + if isinstance(conf_coll, Sequence): + conf_coll = conf_coll[index] + + if isinstance(conf_coll, str): + conf_coll = Path(conf_coll) + + filepath = conf_coll / path + + self.__current_filepath = filepath + self.__file = open(str(filepath), mode) + + # @TODO write below method to duplicate file or template in local to project role file/template + def clone(self, source_scope: Scopes = Scopes.SYS, target_scope: Scopes = Scopes.PROJ, index: int = 0) -> Never: + raise NotImplementedError + +# @TODO rewrite below using DIR_ROOTS var from whereami module +sshd_paths: PathRoles = { + Roles.CONF.name.lower(): { + Scopes.PROJ.name.lower(): [ + PROJ_ROLES / "bootstrap" / "files" / "sshd_config.d", + PROJ_ROLES / "bootstrap" / "templates" / "sshd_config.d" + ], + } +} +sshd = App(Software.sshd, SoftwareRoles.server, sshd_paths) + +class Group: + def __init__(self, group_name: GroupName = GroupName.sudo, gid: int = 27): + self.group_name = group_name + self.id = gid + self.category: Literal["system", "regular"] = "system" + +class User: + def __init__(self, username: UserName = UserName.root.name, password: str = "test", services: list = [Software.sshd.name.lower()], uid: int = 0): + self.exists = True + self.username = username + self.id = uid + self.password = password + self.services: tuple = tuple(services) + self.shell = "/bin/bash" + self.home = "/" + self.category: Literal["system", "regular"] = "regular" + group = Group(username, self.id) + self.primary_group = group + self.supp_groups = None + + if self.supp_groups is None: + self.is_admin = True + elif isinstance(self.supp_groups, Sequence) and GroupName.sudo in self.supp_groups: + self.is_admin = True + else: + self.is_admin = False + + ssh_keys = SSHKeyCollection() + ssh_keys.pull() + self.ssh_keys = ssh_keys + pubkeys = ssh_keys.publish(SSHKeyType.pubkey.name.lower(), datatype=list) + self.__auth_keys: list[str] = list(map(lambda k: k.read_text(), pubkeys)) + privkeys = ssh_keys.publish(SSHKeyType.privkey.name.lower(), datatype=list) + self.__priv_keys: list[str] = list(map(lambda k: str(k), privkeys[0])) + self.__priv_key_pref: int = privkeys[1] + self.__apps = (sshd,) + + def add_keypair(self, private_key, public_key): + raise NotImplementedError + diff --git a/softman.py b/softman.py deleted file mode 100644 index cf3986e..0000000 --- a/softman.py +++ /dev/null @@ -1,182 +0,0 @@ -""" -Library of classes modeling software and software-related -data as represented in or used by Ansible. -""" - -from typing import TypeAlias as Neotype -from typing import TypedDict as Dict -from typing import Never, Union, Callable -from custtypes import ExecutedPath, IdlePath -from enum import Enum -from pathlib import Path, PurePath -from whereami import USER_PATH, PROJ_ROOT -from collections.abc import Sequence - -AppPath: Neotype = Union[ExecutedPath, IdlePath] - -class SoftScope(Enum): - PERSONAL = 0 - LOCAL = 1 - GLOBAL = 2 - -class SoftPathGroup(Enum): - CONFIG = 0 - DATA = 1 - MEM = 2 - EXE = 3 - -_SubAppParams = Dict("_SubAppParams", { - SoftScope.PERSONAL.name: IdlePath | list[IdlePath], - SoftScope.LOCAL.name: IdlePath | list[IdlePath], - SoftScope.GLOBAL.name: IdlePath | list[IdlePath] -}, total=False) -AppParams = Dict("AppParams", { - SoftPathGroup.CONFIG.name: _SubAppParams, - SoftPathGroup.DATA.name: _SubAppParams, - SoftPathGroup.MEM.name: _SubAppParams, - SoftPathGroup.EXE.name: _SubAppParams -}, total=False) - -def __AppsInit(self, CONFIG = None, DATA = None, MEM = None, EXE = None): - self.CONFIG = CONFIG - self.DATA = DATA - self.MEM = MEM - self.EXE = EXE -__app_input = { - SoftPathGroup.CONFIG.name: { - SoftScope.PERSONAL.name: [], - SoftScope.LOCAL.name: [], - SoftScope.GLOBAL.name: [] - }, - SoftPathGroup.DATA.name: { - SoftScope.PERSONAL.name: [], - SoftScope.LOCAL.name: [], - SoftScope.GLOBAL.name: [] - }, - SoftPathGroup.MEM.name: { - SoftScope.PERSONAL.name: [], - SoftScope.LOCAL.name: [], - SoftScope.GLOBAL.name: [] - }, - SoftPathGroup.EXE.name: { - SoftScope.PERSONAL.name: [], - SoftScope.LOCAL.name: [], - SoftScope.GLOBAL.name: [] - }, - "__init__": __AppsInit -} -Apps = type("Apps", (), __app_input) - -# @TODO continue adding magic methods to below class -# @NOTE https://rszalski.github.io/magicmethods/#sequence -class Software (Sequence): - __user_path: ExecutedPath = USER_PATH - - def __init__(self): - self._fqdn: str | None = None - - # @TODO fix NameError for 'Software' in parameter type check - def append(self, name: str, **kwpaths: _SubAppParams) -> AppParams: - keyword_args: AppParams = kwpaths - - app = Apps(**keyword_args) - setattr(self, name, app) - return app - - def __getitem__(self, key: str) -> AppParams | Never: - if hasattr(self, key): - app: Apps = getattr(self, key) - else: - raise KeyError - return app - - def __setitem__(self, key: tuple[str, SoftPathGroup], **value: IdlePath | list[IdlePath]) -> None | Never: - if len(value) < 1 or len(value) > 3: - raise ValueError - - app_params: _SubAppParams = value - - if hasattr(self, key[0]): - app: Apps = getattr(self, key[0]) - - if hasattr(app, key[1]): - app_child: _SubAppParams = getattr(app, key[1]) - - for k, v in app_params.items(): - v = [v] if not isinstance(v, list) else v - app_child[k]: IdlePath | list[IdlePath] = v - setattr(app, key[1], app_child) - else: - raise KeyError - - setattr(self, key[0], app) - else: - raise KeyError - - def __delitem__(self, key: tuple[str | SoftPathGroup]) -> None | Never: - if len(key) < 1 or len(key) > 3: - raise KeyError - - if not hasattr(self, key[0]): - raise KeyError - - if len(key) == 1: - delattr(self, key[0]) - elif len(key) > 1: - app: Apps = getattr(self, key[0]) - delattr(app, key[1]) - setattr(self, key[0], app) - - def show(self, contents: bool = False) -> tuple[str]: - apps: tuple[str] | tuple[Apps] = tuple( - filter( - lambda a: isinstance(getattr(self, a), Apps), - dir(self) - ) - ) - - if contents: - apps = tuple( - map( - lambda a: getattr(self, a), - apps - ) - ) - - return apps - - def __len__(self) -> int: - apps: tuple[str] = tuple( - filter( - lambda a: isinstance(getattr(self, a), Apps), - dir(self) - ) - ) - return len(apps) - - def pop(self) -> Never: - raise NotImplementedError - - def remove(self) -> Never: - raise NotImplementedError - - def __contains__(self) -> Never: - raise NotImplementedError - - def count(self) -> Never: - return NotImplementedError - - def __missing__(self) -> Never: - raise NotImplementedError - - def __iter__(self) -> Never: - raise NotImplementedError - - def reverse(self) -> Never: - raise NotImplementedError - - def sort(self, key: Callable = (lambda e: e), reverse: bool = False) -> Never: - raise NotImplementedError - -class Softs(Enum): - ssh = 0 \ No newline at end of file