ominfra 0.0.0.dev188__py3-none-any.whl → 0.0.0.dev190__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ominfra/__about__.py +1 -0
- ominfra/manage/deploy/apps.py +42 -90
- ominfra/manage/deploy/commands.py +1 -1
- ominfra/manage/deploy/conf/manager.py +20 -12
- ominfra/manage/deploy/conf/specs.py +9 -0
- ominfra/manage/deploy/deploy.py +207 -2
- ominfra/manage/deploy/inject.py +4 -6
- ominfra/manage/deploy/nginx.py +8 -0
- ominfra/manage/deploy/paths/paths.py +5 -1
- ominfra/manage/deploy/specs.py +12 -1
- ominfra/manage/deploy/systemd.py +110 -0
- ominfra/manage/deploy/venvs.py +0 -2
- ominfra/scripts/journald2aws.py +11 -0
- ominfra/scripts/manage.py +489 -152
- ominfra/scripts/supervisor.py +11 -0
- {ominfra-0.0.0.dev188.dist-info → ominfra-0.0.0.dev190.dist-info}/METADATA +4 -3
- {ominfra-0.0.0.dev188.dist-info → ominfra-0.0.0.dev190.dist-info}/RECORD +21 -20
- ominfra/manage/deploy/driver.py +0 -62
- {ominfra-0.0.0.dev188.dist-info → ominfra-0.0.0.dev190.dist-info}/LICENSE +0 -0
- {ominfra-0.0.0.dev188.dist-info → ominfra-0.0.0.dev190.dist-info}/WHEEL +0 -0
- {ominfra-0.0.0.dev188.dist-info → ominfra-0.0.0.dev190.dist-info}/entry_points.txt +0 -0
- {ominfra-0.0.0.dev188.dist-info → ominfra-0.0.0.dev190.dist-info}/top_level.txt +0 -0
    
        ominfra/__about__.py
    CHANGED
    
    
    
        ominfra/manage/deploy/apps.py
    CHANGED
    
    | @@ -1,10 +1,12 @@ | |
| 1 1 | 
             
            # ruff: noqa: UP006 UP007
         | 
| 2 | 
            +
            import dataclasses as dc
         | 
| 2 3 | 
             
            import os.path
         | 
| 3 4 | 
             
            import typing as ta
         | 
| 4 5 |  | 
| 5 6 | 
             
            from omlish.lite.cached import cached_nullary
         | 
| 6 7 | 
             
            from omlish.lite.check import check
         | 
| 7 | 
            -
            from omlish. | 
| 8 | 
            +
            from omlish.lite.json import json_dumps_pretty
         | 
| 9 | 
            +
            from omlish.lite.marshal import ObjMarshalerManager
         | 
| 8 10 |  | 
| 9 11 | 
             
            from .conf.manager import DeployConfManager
         | 
| 10 12 | 
             
            from .git import DeployGitManager
         | 
| @@ -20,39 +22,31 @@ class DeployAppManager(DeployPathOwner): | |
| 20 22 | 
             
                def __init__(
         | 
| 21 23 | 
             
                        self,
         | 
| 22 24 | 
             
                        *,
         | 
| 23 | 
            -
                        conf: DeployConfManager,
         | 
| 24 25 | 
             
                        git: DeployGitManager,
         | 
| 25 26 | 
             
                        venvs: DeployVenvManager,
         | 
| 27 | 
            +
                        conf: DeployConfManager,
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                        msh: ObjMarshalerManager,
         | 
| 26 30 | 
             
                ) -> None:
         | 
| 27 31 | 
             
                    super().__init__()
         | 
| 28 32 |  | 
| 29 | 
            -
                    self._conf = conf
         | 
| 30 33 | 
             
                    self._git = git
         | 
| 31 34 | 
             
                    self._venvs = venvs
         | 
| 35 | 
            +
                    self._conf = conf
         | 
| 32 36 |  | 
| 33 | 
            -
             | 
| 34 | 
            -
             | 
| 35 | 
            -
                _APP_DIR_STR = 'apps/@app/@time--@app-rev--@app-key/'
         | 
| 36 | 
            -
                _APP_DIR = DeployPath.parse(_APP_DIR_STR)
         | 
| 37 | 
            +
                    self._msh = msh
         | 
| 37 38 |  | 
| 38 | 
            -
                 | 
| 39 | 
            -
                _DEPLOY_DIR = DeployPath.parse(_DEPLOY_DIR_STR)
         | 
| 39 | 
            +
                #
         | 
| 40 40 |  | 
| 41 | 
            -
                 | 
| 42 | 
            -
                _CONF_DEPLOY_DIR = DeployPath.parse(f'{_DEPLOY_DIR_STR}conf/@conf/')
         | 
| 41 | 
            +
                APP_DIR = DeployPath.parse('apps/@app/@time--@app-rev--@app-key/')
         | 
| 43 42 |  | 
| 44 43 | 
             
                @cached_nullary
         | 
| 45 44 | 
             
                def get_owned_deploy_paths(self) -> ta.AbstractSet[DeployPath]:
         | 
| 46 45 | 
             
                    return {
         | 
| 47 | 
            -
                        self. | 
| 48 | 
            -
             | 
| 49 | 
            -
                        self._DEPLOY_DIR,
         | 
| 50 | 
            -
             | 
| 51 | 
            -
                        self._APP_DEPLOY_LINK,
         | 
| 52 | 
            -
                        self._CONF_DEPLOY_DIR,
         | 
| 46 | 
            +
                        self.APP_DIR,
         | 
| 53 47 |  | 
| 54 48 | 
             
                        *[
         | 
| 55 | 
            -
                            DeployPath.parse(f'{self. | 
| 49 | 
            +
                            DeployPath.parse(f'{self.APP_DIR}{sfx}/')
         | 
| 56 50 | 
             
                            for sfx in [
         | 
| 57 51 | 
             
                                'conf',
         | 
| 58 52 | 
             
                                'git',
         | 
| @@ -63,115 +57,73 @@ class DeployAppManager(DeployPathOwner): | |
| 63 57 |  | 
| 64 58 | 
             
                #
         | 
| 65 59 |  | 
| 60 | 
            +
                @dc.dataclass(frozen=True)
         | 
| 61 | 
            +
                class PreparedApp:
         | 
| 62 | 
            +
                    app_dir: str
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                    git_dir: ta.Optional[str] = None
         | 
| 65 | 
            +
                    venv_dir: ta.Optional[str] = None
         | 
| 66 | 
            +
                    conf_dir: ta.Optional[str] = None
         | 
| 67 | 
            +
             | 
| 66 68 | 
             
                async def prepare_app(
         | 
| 67 69 | 
             
                        self,
         | 
| 68 70 | 
             
                        spec: DeployAppSpec,
         | 
| 69 71 | 
             
                        home: DeployHome,
         | 
| 70 72 | 
             
                        tags: DeployTagMap,
         | 
| 71 | 
            -
                ) ->  | 
| 72 | 
            -
                     | 
| 73 | 
            -
             | 
| 74 | 
            -
                    def build_path(pth: DeployPath) -> str:
         | 
| 75 | 
            -
                        return os.path.join(home, pth.render(tags))
         | 
| 76 | 
            -
             | 
| 77 | 
            -
                    app_dir = build_path(self._APP_DIR)
         | 
| 78 | 
            -
                    deploy_dir = build_path(self._DEPLOY_DIR)
         | 
| 79 | 
            -
                    app_deploy_link = build_path(self._APP_DEPLOY_LINK)
         | 
| 73 | 
            +
                ) -> PreparedApp:
         | 
| 74 | 
            +
                    spec_json = json_dumps_pretty(self._msh.marshal_obj(spec))
         | 
| 80 75 |  | 
| 81 76 | 
             
                    #
         | 
| 82 77 |  | 
| 83 | 
            -
                     | 
| 84 | 
            -
             | 
| 85 | 
            -
                    deploying_link = os.path.join(home, 'deploys/deploying')
         | 
| 86 | 
            -
                    if os.path.exists(deploying_link):
         | 
| 87 | 
            -
                        os.unlink(deploying_link)
         | 
| 88 | 
            -
                    relative_symlink(
         | 
| 89 | 
            -
                        deploy_dir,
         | 
| 90 | 
            -
                        deploying_link,
         | 
| 91 | 
            -
                        target_is_directory=True,
         | 
| 92 | 
            -
                        make_dirs=True,
         | 
| 93 | 
            -
                    )
         | 
| 78 | 
            +
                    check.non_empty_str(home)
         | 
| 94 79 |  | 
| 95 | 
            -
                     | 
| 80 | 
            +
                    app_dir = os.path.join(home, self.APP_DIR.render(tags))
         | 
| 96 81 |  | 
| 97 | 
            -
                    os.makedirs(app_dir)
         | 
| 98 | 
            -
                    relative_symlink(
         | 
| 99 | 
            -
                        app_dir,
         | 
| 100 | 
            -
                        app_deploy_link,
         | 
| 101 | 
            -
                        target_is_directory=True,
         | 
| 102 | 
            -
                        make_dirs=True,
         | 
| 103 | 
            -
                    )
         | 
| 82 | 
            +
                    os.makedirs(app_dir, exist_ok=True)
         | 
| 104 83 |  | 
| 105 84 | 
             
                    #
         | 
| 106 85 |  | 
| 107 | 
            -
                     | 
| 108 | 
            -
             | 
| 86 | 
            +
                    rkw: ta.Dict[str, ta.Any] = dict(
         | 
| 87 | 
            +
                        app_dir=app_dir,
         | 
| 88 | 
            +
                    )
         | 
| 109 89 |  | 
| 110 90 | 
             
                    #
         | 
| 111 91 |  | 
| 112 | 
            -
                     | 
| 113 | 
            -
                     | 
| 114 | 
            -
             | 
| 115 | 
            -
                    #         shutil.copy2(
         | 
| 116 | 
            -
                    #             lp,
         | 
| 117 | 
            -
                    #             os.path.join(dst, os.path.relpath(lp, src)),
         | 
| 118 | 
            -
                    #             follow_symlinks=False,
         | 
| 119 | 
            -
                    #         )
         | 
| 120 | 
            -
                    #
         | 
| 121 | 
            -
                    #     for dp, dns, fns in os.walk(src, followlinks=False):
         | 
| 122 | 
            -
                    #         for fn in fns:
         | 
| 123 | 
            -
                    #             mirror_link(os.path.join(dp, fn))
         | 
| 124 | 
            -
                    #
         | 
| 125 | 
            -
                    #         for dn in dns:
         | 
| 126 | 
            -
                    #             dp2 = os.path.join(dp, dn)
         | 
| 127 | 
            -
                    #             if os.path.islink(dp2):
         | 
| 128 | 
            -
                    #                 mirror_link(dp2)
         | 
| 129 | 
            -
                    #             else:
         | 
| 130 | 
            -
                    #                 os.makedirs(os.path.join(dst, os.path.relpath(dp2, src)))
         | 
| 131 | 
            -
             | 
| 132 | 
            -
                    current_link = os.path.join(home, 'deploys/current')
         | 
| 133 | 
            -
             | 
| 134 | 
            -
                    # if os.path.exists(current_link):
         | 
| 135 | 
            -
                    #     mirror_symlinks(
         | 
| 136 | 
            -
                    #         os.path.join(current_link, 'conf'),
         | 
| 137 | 
            -
                    #         conf_tag_dir,
         | 
| 138 | 
            -
                    #     )
         | 
| 139 | 
            -
                    #     mirror_symlinks(
         | 
| 140 | 
            -
                    #         os.path.join(current_link, 'apps'),
         | 
| 141 | 
            -
                    #         os.path.join(deploy_dir, 'apps'),
         | 
| 142 | 
            -
                    #     )
         | 
| 92 | 
            +
                    spec_file = os.path.join(app_dir, 'spec.json')
         | 
| 93 | 
            +
                    with open(spec_file, 'w') as f:  # noqa
         | 
| 94 | 
            +
                        f.write(spec_json)
         | 
| 143 95 |  | 
| 144 96 | 
             
                    #
         | 
| 145 97 |  | 
| 146 | 
            -
                     | 
| 98 | 
            +
                    git_dir = os.path.join(app_dir, 'git')
         | 
| 99 | 
            +
                    rkw.update(git_dir=git_dir)
         | 
| 147 100 | 
             
                    await self._git.checkout(
         | 
| 148 101 | 
             
                        spec.git,
         | 
| 149 102 | 
             
                        home,
         | 
| 150 | 
            -
                         | 
| 103 | 
            +
                        git_dir,
         | 
| 151 104 | 
             
                    )
         | 
| 152 105 |  | 
| 153 106 | 
             
                    #
         | 
| 154 107 |  | 
| 155 108 | 
             
                    if spec.venv is not None:
         | 
| 156 | 
            -
                         | 
| 109 | 
            +
                        venv_dir = os.path.join(app_dir, 'venv')
         | 
| 110 | 
            +
                        rkw.update(venv_dir=venv_dir)
         | 
| 157 111 | 
             
                        await self._venvs.setup_venv(
         | 
| 158 112 | 
             
                            spec.venv,
         | 
| 159 | 
            -
                             | 
| 160 | 
            -
                             | 
| 161 | 
            -
                            app_venv_dir,
         | 
| 113 | 
            +
                            git_dir,
         | 
| 114 | 
            +
                            venv_dir,
         | 
| 162 115 | 
             
                        )
         | 
| 163 116 |  | 
| 164 117 | 
             
                    #
         | 
| 165 118 |  | 
| 166 119 | 
             
                    if spec.conf is not None:
         | 
| 167 | 
            -
                         | 
| 120 | 
            +
                        conf_dir = os.path.join(app_dir, 'conf')
         | 
| 121 | 
            +
                        rkw.update(conf_dir=conf_dir)
         | 
| 168 122 | 
             
                        await self._conf.write_app_conf(
         | 
| 169 123 | 
             
                            spec.conf,
         | 
| 170 | 
            -
                             | 
| 171 | 
            -
                            app_conf_dir,
         | 
| 172 | 
            -
                            deploy_conf_dir,
         | 
| 124 | 
            +
                            conf_dir,
         | 
| 173 125 | 
             
                        )
         | 
| 174 126 |  | 
| 175 127 | 
             
                    #
         | 
| 176 128 |  | 
| 177 | 
            -
                     | 
| 129 | 
            +
                    return DeployAppManager.PreparedApp(**rkw)
         | 
| @@ -24,6 +24,8 @@ from omlish.lite.json import json_dumps_pretty | |
| 24 24 | 
             
            from omlish.lite.strings import strip_with_newline
         | 
| 25 25 | 
             
            from omlish.os.paths import is_path_in_dir
         | 
| 26 26 | 
             
            from omlish.os.paths import relative_symlink
         | 
| 27 | 
            +
            from omserv.nginx.configs import NginxConfigItems
         | 
| 28 | 
            +
            from omserv.nginx.configs import render_nginx_config_str
         | 
| 27 29 |  | 
| 28 30 | 
             
            from ....configs import render_ini_config
         | 
| 29 31 | 
             
            from ..paths.paths import DeployPath
         | 
| @@ -37,6 +39,7 @@ from .specs import DeployAppConfLink | |
| 37 39 | 
             
            from .specs import DeployAppConfSpec
         | 
| 38 40 | 
             
            from .specs import IniDeployAppConfContent
         | 
| 39 41 | 
             
            from .specs import JsonDeployAppConfContent
         | 
| 42 | 
            +
            from .specs import NginxDeployAppConfContent
         | 
| 40 43 | 
             
            from .specs import RawDeployAppConfContent
         | 
| 41 44 |  | 
| 42 45 |  | 
| @@ -51,6 +54,10 @@ class DeployConfManager: | |
| 51 54 | 
             
                    elif isinstance(ac, IniDeployAppConfContent):
         | 
| 52 55 | 
             
                        return strip_with_newline(render_ini_config(ac.sections))
         | 
| 53 56 |  | 
| 57 | 
            +
                    elif isinstance(ac, NginxDeployAppConfContent):
         | 
| 58 | 
            +
                        ni = NginxConfigItems.of(ac.items)
         | 
| 59 | 
            +
                        return strip_with_newline(render_nginx_config_str(ni))
         | 
| 60 | 
            +
             | 
| 54 61 | 
             
                    else:
         | 
| 55 62 | 
             
                        raise TypeError(ac)
         | 
| 56 63 |  | 
| @@ -69,6 +76,17 @@ class DeployConfManager: | |
| 69 76 | 
             
                    with open(conf_file, 'w') as f:  # noqa
         | 
| 70 77 | 
             
                        f.write(body)
         | 
| 71 78 |  | 
| 79 | 
            +
                async def write_app_conf(
         | 
| 80 | 
            +
                        self,
         | 
| 81 | 
            +
                        spec: DeployAppConfSpec,
         | 
| 82 | 
            +
                        app_conf_dir: str,
         | 
| 83 | 
            +
                ) -> None:
         | 
| 84 | 
            +
                    for acf in spec.files or []:
         | 
| 85 | 
            +
                        await self._write_app_conf_file(
         | 
| 86 | 
            +
                            acf,
         | 
| 87 | 
            +
                            app_conf_dir,
         | 
| 88 | 
            +
                        )
         | 
| 89 | 
            +
             | 
| 72 90 | 
             
                #
         | 
| 73 91 |  | 
| 74 92 | 
             
                class _ComputedConfLink(ta.NamedTuple):
         | 
| @@ -178,23 +196,13 @@ class DeployConfManager: | |
| 178 196 | 
             
                        make_dirs=True,
         | 
| 179 197 | 
             
                    )
         | 
| 180 198 |  | 
| 181 | 
            -
                 | 
| 182 | 
            -
             | 
| 183 | 
            -
                async def write_app_conf(
         | 
| 199 | 
            +
                async def link_app_conf(
         | 
| 184 200 | 
             
                        self,
         | 
| 185 201 | 
             
                        spec: DeployAppConfSpec,
         | 
| 186 202 | 
             
                        tags: DeployTagMap,
         | 
| 187 203 | 
             
                        app_conf_dir: str,
         | 
| 188 204 | 
             
                        conf_link_dir: str,
         | 
| 189 | 
            -
                ) | 
| 190 | 
            -
                    for acf in spec.files or []:
         | 
| 191 | 
            -
                        await self._write_app_conf_file(
         | 
| 192 | 
            -
                            acf,
         | 
| 193 | 
            -
                            app_conf_dir,
         | 
| 194 | 
            -
                        )
         | 
| 195 | 
            -
             | 
| 196 | 
            -
                    #
         | 
| 197 | 
            -
             | 
| 205 | 
            +
                ):
         | 
| 198 206 | 
             
                    for link in spec.links or []:
         | 
| 199 207 | 
             
                        await self._make_app_conf_link(
         | 
| 200 208 | 
             
                            link,
         | 
| @@ -44,6 +44,15 @@ class IniDeployAppConfContent(DeployAppConfContent): | |
| 44 44 | 
             
                sections: IniConfigSectionSettingsMap
         | 
| 45 45 |  | 
| 46 46 |  | 
| 47 | 
            +
            #
         | 
| 48 | 
            +
             | 
| 49 | 
            +
             | 
| 50 | 
            +
            @register_single_field_type_obj_marshaler('items')
         | 
| 51 | 
            +
            @dc.dataclass(frozen=True)
         | 
| 52 | 
            +
            class NginxDeployAppConfContent(DeployAppConfContent):
         | 
| 53 | 
            +
                items: ta.Any
         | 
| 54 | 
            +
             | 
| 55 | 
            +
             | 
| 47 56 | 
             
            ##
         | 
| 48 57 |  | 
| 49 58 |  | 
    
        ominfra/manage/deploy/deploy.py
    CHANGED
    
    | @@ -1,10 +1,31 @@ | |
| 1 1 | 
             
            # ruff: noqa: UP006 UP007
         | 
| 2 2 | 
             
            import datetime
         | 
| 3 | 
            +
            import os.path
         | 
| 3 4 | 
             
            import typing as ta
         | 
| 4 5 |  | 
| 6 | 
            +
            from omlish.lite.cached import cached_nullary
         | 
| 7 | 
            +
            from omlish.lite.check import check
         | 
| 8 | 
            +
            from omlish.lite.json import json_dumps_pretty
         | 
| 9 | 
            +
            from omlish.lite.marshal import ObjMarshalerManager
         | 
| 5 10 | 
             
            from omlish.lite.typing import Func0
         | 
| 11 | 
            +
            from omlish.lite.typing import Func1
         | 
| 12 | 
            +
            from omlish.os.paths import relative_symlink
         | 
| 6 13 |  | 
| 14 | 
            +
            from .apps import DeployAppManager
         | 
| 15 | 
            +
            from .conf.manager import DeployConfManager
         | 
| 16 | 
            +
            from .paths.manager import DeployPathsManager
         | 
| 17 | 
            +
            from .paths.owners import DeployPathOwner
         | 
| 18 | 
            +
            from .paths.paths import DeployPath
         | 
| 19 | 
            +
            from .specs import DeployAppSpec
         | 
| 20 | 
            +
            from .specs import DeploySpec
         | 
| 21 | 
            +
            from .systemd import DeploySystemdManager
         | 
| 22 | 
            +
            from .tags import DeployAppRev
         | 
| 23 | 
            +
            from .tags import DeployTagMap
         | 
| 7 24 | 
             
            from .tags import DeployTime
         | 
| 25 | 
            +
            from .types import DeployHome
         | 
| 26 | 
            +
             | 
| 27 | 
            +
             | 
| 28 | 
            +
            ##
         | 
| 8 29 |  | 
| 9 30 |  | 
| 10 31 | 
             
            DEPLOY_TAG_DATETIME_FMT = '%Y%m%dT%H%M%SZ'
         | 
| @@ -13,17 +34,52 @@ DEPLOY_TAG_DATETIME_FMT = '%Y%m%dT%H%M%SZ' | |
| 13 34 | 
             
            DeployManagerUtcClock = ta.NewType('DeployManagerUtcClock', Func0[datetime.datetime])
         | 
| 14 35 |  | 
| 15 36 |  | 
| 16 | 
            -
            class DeployManager:
         | 
| 37 | 
            +
            class DeployManager(DeployPathOwner):
         | 
| 17 38 | 
             
                def __init__(
         | 
| 18 39 | 
             
                        self,
         | 
| 19 40 | 
             
                        *,
         | 
| 20 | 
            -
             | 
| 21 41 | 
             
                        utc_clock: ta.Optional[DeployManagerUtcClock] = None,
         | 
| 22 42 | 
             
                ):
         | 
| 23 43 | 
             
                    super().__init__()
         | 
| 24 44 |  | 
| 25 45 | 
             
                    self._utc_clock = utc_clock
         | 
| 26 46 |  | 
| 47 | 
            +
                #
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                DEPLOYS_DIR = DeployPath.parse('deploys/')
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                CURRENT_DEPLOY_LINK = DeployPath.parse(f'{DEPLOYS_DIR}current')
         | 
| 52 | 
            +
                DEPLOYING_DEPLOY_LINK = DeployPath.parse(f'{DEPLOYS_DIR}deploying')
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                DEPLOY_DIR = DeployPath.parse(f'{DEPLOYS_DIR}@time--@deploy-key/')
         | 
| 55 | 
            +
                DEPLOY_SPEC_FILE = DeployPath.parse(f'{DEPLOY_DIR}spec.json')
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                APPS_DEPLOY_DIR = DeployPath.parse(f'{DEPLOY_DIR}apps/')
         | 
| 58 | 
            +
                APP_DEPLOY_LINK = DeployPath.parse(f'{APPS_DEPLOY_DIR}@app')
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                CONFS_DEPLOY_DIR = DeployPath.parse(f'{DEPLOY_DIR}conf/')
         | 
| 61 | 
            +
                CONF_DEPLOY_DIR = DeployPath.parse(f'{CONFS_DEPLOY_DIR}@conf/')
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                @cached_nullary
         | 
| 64 | 
            +
                def get_owned_deploy_paths(self) -> ta.AbstractSet[DeployPath]:
         | 
| 65 | 
            +
                    return {
         | 
| 66 | 
            +
                        self.DEPLOYS_DIR,
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                        self.CURRENT_DEPLOY_LINK,
         | 
| 69 | 
            +
                        self.DEPLOYING_DEPLOY_LINK,
         | 
| 70 | 
            +
             | 
| 71 | 
            +
                        self.DEPLOY_DIR,
         | 
| 72 | 
            +
                        self.DEPLOY_SPEC_FILE,
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                        self.APPS_DEPLOY_DIR,
         | 
| 75 | 
            +
                        self.APP_DEPLOY_LINK,
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                        self.CONFS_DEPLOY_DIR,
         | 
| 78 | 
            +
                        self.CONF_DEPLOY_DIR,
         | 
| 79 | 
            +
                    }
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                #
         | 
| 82 | 
            +
             | 
| 27 83 | 
             
                def _utc_now(self) -> datetime.datetime:
         | 
| 28 84 | 
             
                    if self._utc_clock is not None:
         | 
| 29 85 | 
             
                        return self._utc_clock()  # noqa
         | 
| @@ -32,3 +88,152 @@ class DeployManager: | |
| 32 88 |  | 
| 33 89 | 
             
                def make_deploy_time(self) -> DeployTime:
         | 
| 34 90 | 
             
                    return DeployTime(self._utc_now().strftime(DEPLOY_TAG_DATETIME_FMT))
         | 
| 91 | 
            +
             | 
| 92 | 
            +
             | 
| 93 | 
            +
            ##
         | 
| 94 | 
            +
             | 
| 95 | 
            +
             | 
| 96 | 
            +
            class DeployDriverFactory(Func1[DeploySpec, ta.ContextManager['DeployDriver']]):
         | 
| 97 | 
            +
                pass
         | 
| 98 | 
            +
             | 
| 99 | 
            +
             | 
| 100 | 
            +
            class DeployDriver:
         | 
| 101 | 
            +
                def __init__(
         | 
| 102 | 
            +
                        self,
         | 
| 103 | 
            +
                        *,
         | 
| 104 | 
            +
                        spec: DeploySpec,
         | 
| 105 | 
            +
                        home: DeployHome,
         | 
| 106 | 
            +
                        time: DeployTime,
         | 
| 107 | 
            +
             | 
| 108 | 
            +
                        deploys: DeployManager,
         | 
| 109 | 
            +
                        paths: DeployPathsManager,
         | 
| 110 | 
            +
                        apps: DeployAppManager,
         | 
| 111 | 
            +
                        conf: DeployConfManager,
         | 
| 112 | 
            +
                        systemd: DeploySystemdManager,
         | 
| 113 | 
            +
             | 
| 114 | 
            +
                        msh: ObjMarshalerManager,
         | 
| 115 | 
            +
                ) -> None:
         | 
| 116 | 
            +
                    super().__init__()
         | 
| 117 | 
            +
             | 
| 118 | 
            +
                    self._spec = spec
         | 
| 119 | 
            +
                    self._home = home
         | 
| 120 | 
            +
                    self._time = time
         | 
| 121 | 
            +
             | 
| 122 | 
            +
                    self._deploys = deploys
         | 
| 123 | 
            +
                    self._paths = paths
         | 
| 124 | 
            +
                    self._apps = apps
         | 
| 125 | 
            +
                    self._conf = conf
         | 
| 126 | 
            +
                    self._systemd = systemd
         | 
| 127 | 
            +
             | 
| 128 | 
            +
                    self._msh = msh
         | 
| 129 | 
            +
             | 
| 130 | 
            +
                #
         | 
| 131 | 
            +
             | 
| 132 | 
            +
                @property
         | 
| 133 | 
            +
                def deploy_tags(self) -> DeployTagMap:
         | 
| 134 | 
            +
                    return DeployTagMap(
         | 
| 135 | 
            +
                        self._time,
         | 
| 136 | 
            +
                        self._spec.key(),
         | 
| 137 | 
            +
                    )
         | 
| 138 | 
            +
             | 
| 139 | 
            +
                def render_deploy_path(self, pth: DeployPath, tags: ta.Optional[DeployTagMap] = None) -> str:
         | 
| 140 | 
            +
                    return os.path.join(self._home, pth.render(tags if tags is not None else self.deploy_tags))
         | 
| 141 | 
            +
             | 
| 142 | 
            +
                @property
         | 
| 143 | 
            +
                def deploy_dir(self) -> str:
         | 
| 144 | 
            +
                    return self.render_deploy_path(self._deploys.DEPLOY_DIR)
         | 
| 145 | 
            +
             | 
| 146 | 
            +
                #
         | 
| 147 | 
            +
             | 
| 148 | 
            +
                async def drive_deploy(self) -> None:
         | 
| 149 | 
            +
                    spec_json = json_dumps_pretty(self._msh.marshal_obj(self._spec))
         | 
| 150 | 
            +
             | 
| 151 | 
            +
                    #
         | 
| 152 | 
            +
             | 
| 153 | 
            +
                    self._paths.validate_deploy_paths()
         | 
| 154 | 
            +
             | 
| 155 | 
            +
                    #
         | 
| 156 | 
            +
             | 
| 157 | 
            +
                    os.makedirs(self.deploy_dir)
         | 
| 158 | 
            +
             | 
| 159 | 
            +
                    #
         | 
| 160 | 
            +
             | 
| 161 | 
            +
                    spec_file = self.render_deploy_path(self._deploys.DEPLOY_SPEC_FILE)
         | 
| 162 | 
            +
                    with open(spec_file, 'w') as f:  # noqa
         | 
| 163 | 
            +
                        f.write(spec_json)
         | 
| 164 | 
            +
             | 
| 165 | 
            +
                    #
         | 
| 166 | 
            +
             | 
| 167 | 
            +
                    deploying_link = self.render_deploy_path(self._deploys.DEPLOYING_DEPLOY_LINK)
         | 
| 168 | 
            +
                    if os.path.exists(deploying_link):
         | 
| 169 | 
            +
                        os.unlink(deploying_link)
         | 
| 170 | 
            +
                    relative_symlink(
         | 
| 171 | 
            +
                        self.deploy_dir,
         | 
| 172 | 
            +
                        deploying_link,
         | 
| 173 | 
            +
                        target_is_directory=True,
         | 
| 174 | 
            +
                        make_dirs=True,
         | 
| 175 | 
            +
                    )
         | 
| 176 | 
            +
             | 
| 177 | 
            +
                    #
         | 
| 178 | 
            +
             | 
| 179 | 
            +
                    for md in [
         | 
| 180 | 
            +
                        self._deploys.APPS_DEPLOY_DIR,
         | 
| 181 | 
            +
                        self._deploys.CONFS_DEPLOY_DIR,
         | 
| 182 | 
            +
                    ]:
         | 
| 183 | 
            +
                        os.makedirs(self.render_deploy_path(md))
         | 
| 184 | 
            +
             | 
| 185 | 
            +
                    #
         | 
| 186 | 
            +
             | 
| 187 | 
            +
                    for app in self._spec.apps:
         | 
| 188 | 
            +
                        await self.drive_app_deploy(app)
         | 
| 189 | 
            +
             | 
| 190 | 
            +
                    #
         | 
| 191 | 
            +
             | 
| 192 | 
            +
                    current_link = self.render_deploy_path(self._deploys.CURRENT_DEPLOY_LINK)
         | 
| 193 | 
            +
                    os.replace(deploying_link, current_link)
         | 
| 194 | 
            +
             | 
| 195 | 
            +
                    #
         | 
| 196 | 
            +
             | 
| 197 | 
            +
                    await self._systemd.sync_systemd(
         | 
| 198 | 
            +
                        self._spec.systemd,
         | 
| 199 | 
            +
                        self._home,
         | 
| 200 | 
            +
                        os.path.join(self.deploy_dir, 'conf', 'systemd'),  # FIXME
         | 
| 201 | 
            +
                    )
         | 
| 202 | 
            +
             | 
| 203 | 
            +
                #
         | 
| 204 | 
            +
             | 
| 205 | 
            +
                async def drive_app_deploy(self, app: DeployAppSpec) -> None:
         | 
| 206 | 
            +
                    app_tags = self.deploy_tags.add(
         | 
| 207 | 
            +
                        app.app,
         | 
| 208 | 
            +
                        app.key(),
         | 
| 209 | 
            +
                        DeployAppRev(app.git.rev),
         | 
| 210 | 
            +
                    )
         | 
| 211 | 
            +
             | 
| 212 | 
            +
                    #
         | 
| 213 | 
            +
             | 
| 214 | 
            +
                    da = await self._apps.prepare_app(
         | 
| 215 | 
            +
                        app,
         | 
| 216 | 
            +
                        self._home,
         | 
| 217 | 
            +
                        app_tags,
         | 
| 218 | 
            +
                    )
         | 
| 219 | 
            +
             | 
| 220 | 
            +
                    #
         | 
| 221 | 
            +
             | 
| 222 | 
            +
                    app_link = self.render_deploy_path(self._deploys.APP_DEPLOY_LINK, app_tags)
         | 
| 223 | 
            +
                    relative_symlink(
         | 
| 224 | 
            +
                        da.app_dir,
         | 
| 225 | 
            +
                        app_link,
         | 
| 226 | 
            +
                        target_is_directory=True,
         | 
| 227 | 
            +
                        make_dirs=True,
         | 
| 228 | 
            +
                    )
         | 
| 229 | 
            +
             | 
| 230 | 
            +
                    #
         | 
| 231 | 
            +
             | 
| 232 | 
            +
                    deploy_conf_dir = self.render_deploy_path(self._deploys.CONFS_DEPLOY_DIR)
         | 
| 233 | 
            +
                    if app.conf is not None:
         | 
| 234 | 
            +
                        await self._conf.link_app_conf(
         | 
| 235 | 
            +
                            app.conf,
         | 
| 236 | 
            +
                            app_tags,
         | 
| 237 | 
            +
                            check.non_empty_str(da.conf_dir),
         | 
| 238 | 
            +
                            deploy_conf_dir,
         | 
| 239 | 
            +
                        )
         | 
    
        ominfra/manage/deploy/inject.py
    CHANGED
    
    | @@ -16,15 +16,16 @@ from .commands import DeployCommand | |
| 16 16 | 
             
            from .commands import DeployCommandExecutor
         | 
| 17 17 | 
             
            from .conf.inject import bind_deploy_conf
         | 
| 18 18 | 
             
            from .config import DeployConfig
         | 
| 19 | 
            +
            from .deploy import DeployDriver
         | 
| 20 | 
            +
            from .deploy import DeployDriverFactory
         | 
| 19 21 | 
             
            from .deploy import DeployManager
         | 
| 20 | 
            -
            from .driver import DeployDriver
         | 
| 21 | 
            -
            from .driver import DeployDriverFactory
         | 
| 22 22 | 
             
            from .git import DeployGitManager
         | 
| 23 23 | 
             
            from .inject_ import bind_deploy_manager
         | 
| 24 24 | 
             
            from .interp import InterpCommand
         | 
| 25 25 | 
             
            from .interp import InterpCommandExecutor
         | 
| 26 26 | 
             
            from .paths.inject import bind_deploy_paths
         | 
| 27 27 | 
             
            from .specs import DeploySpec
         | 
| 28 | 
            +
            from .systemd import DeploySystemdManager
         | 
| 28 29 | 
             
            from .tags import DeployTime
         | 
| 29 30 | 
             
            from .tmp import DeployHomeAtomics
         | 
| 30 31 | 
             
            from .tmp import DeployTmpManager
         | 
| @@ -101,13 +102,10 @@ def bind_deploy( | |
| 101 102 |  | 
| 102 103 | 
             
                lst.extend([
         | 
| 103 104 | 
             
                    bind_deploy_manager(DeployAppManager),
         | 
| 104 | 
            -
             | 
| 105 105 | 
             
                    bind_deploy_manager(DeployGitManager),
         | 
| 106 | 
            -
             | 
| 107 106 | 
             
                    bind_deploy_manager(DeployManager),
         | 
| 108 | 
            -
             | 
| 107 | 
            +
                    bind_deploy_manager(DeploySystemdManager),
         | 
| 109 108 | 
             
                    bind_deploy_manager(DeployTmpManager),
         | 
| 110 | 
            -
             | 
| 111 109 | 
             
                    bind_deploy_manager(DeployVenvManager),
         | 
| 112 110 | 
             
                ])
         | 
| 113 111 |  | 
| @@ -33,6 +33,10 @@ class DeployPathError(Exception): | |
| 33 33 |  | 
| 34 34 |  | 
| 35 35 | 
             
            class DeployPathRenderable(abc.ABC):
         | 
| 36 | 
            +
                @cached_nullary
         | 
| 37 | 
            +
                def __str__(self) -> str:
         | 
| 38 | 
            +
                    return self.render(None)
         | 
| 39 | 
            +
             | 
| 36 40 | 
             
                @abc.abstractmethod
         | 
| 37 41 | 
             
                def render(self, tags: ta.Optional[DeployTagMap] = None) -> str:
         | 
| 38 42 | 
             
                    raise NotImplementedError
         | 
| @@ -174,7 +178,7 @@ class FileDeployPathPart(DeployPathPart): | |
| 174 178 |  | 
| 175 179 |  | 
| 176 180 | 
             
            @dc.dataclass(frozen=True)
         | 
| 177 | 
            -
            class DeployPath:
         | 
| 181 | 
            +
            class DeployPath(DeployPathRenderable):
         | 
| 178 182 | 
             
                parts: ta.Sequence[DeployPathPart]
         | 
| 179 183 |  | 
| 180 184 | 
             
                @property
         | 
    
        ominfra/manage/deploy/specs.py
    CHANGED
    
    | @@ -94,11 +94,22 @@ class DeployAppSpec(DeploySpecKeyed[DeployAppKey]): | |
| 94 94 | 
             
            ##
         | 
| 95 95 |  | 
| 96 96 |  | 
| 97 | 
            +
            @dc.dataclass(frozen=True)
         | 
| 98 | 
            +
            class DeploySystemdSpec:
         | 
| 99 | 
            +
                # ~/.config/systemd/user/
         | 
| 100 | 
            +
                unit_dir: ta.Optional[str] = None
         | 
| 101 | 
            +
             | 
| 102 | 
            +
             | 
| 103 | 
            +
            ##
         | 
| 104 | 
            +
             | 
| 105 | 
            +
             | 
| 97 106 | 
             
            @dc.dataclass(frozen=True)
         | 
| 98 107 | 
             
            class DeploySpec(DeploySpecKeyed[DeployKey]):
         | 
| 99 108 | 
             
                home: DeployHome
         | 
| 100 109 |  | 
| 101 | 
            -
                apps: ta.Sequence[DeployAppSpec]
         | 
| 110 | 
            +
                apps: ta.Sequence[DeployAppSpec] = ()
         | 
| 111 | 
            +
             | 
| 112 | 
            +
                systemd: ta.Optional[DeploySystemdSpec] = None
         | 
| 102 113 |  | 
| 103 114 | 
             
                def __post_init__(self) -> None:
         | 
| 104 115 | 
             
                    check.non_empty_str(self.home)
         |