ominfra 0.0.0.dev171__py3-none-any.whl → 0.0.0.dev173__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/manage/deploy/apps.py +48 -80
- ominfra/manage/deploy/conf.py +55 -50
- ominfra/manage/deploy/deploy.py +45 -1
- ominfra/manage/deploy/paths/paths.py +39 -45
- ominfra/manage/deploy/paths/types.py +8 -0
- ominfra/manage/deploy/specs.py +47 -13
- ominfra/manage/deploy/tags.py +225 -0
- ominfra/manage/deploy/types.py +0 -30
- ominfra/manage/targets/bestpython.sh +6 -9
- ominfra/scripts/manage.py +489 -200
- {ominfra-0.0.0.dev171.dist-info → ominfra-0.0.0.dev173.dist-info}/METADATA +3 -3
- {ominfra-0.0.0.dev171.dist-info → ominfra-0.0.0.dev173.dist-info}/RECORD +16 -14
- {ominfra-0.0.0.dev171.dist-info → ominfra-0.0.0.dev173.dist-info}/LICENSE +0 -0
- {ominfra-0.0.0.dev171.dist-info → ominfra-0.0.0.dev173.dist-info}/WHEEL +0 -0
- {ominfra-0.0.0.dev171.dist-info → ominfra-0.0.0.dev173.dist-info}/entry_points.txt +0 -0
- {ominfra-0.0.0.dev171.dist-info → ominfra-0.0.0.dev173.dist-info}/top_level.txt +0 -0
    
        ominfra/manage/deploy/apps.py
    CHANGED
    
    | @@ -1,7 +1,5 @@ | |
| 1 1 | 
             
            # ruff: noqa: UP006 UP007
         | 
| 2 | 
            -
            import datetime
         | 
| 3 2 | 
             
            import os.path
         | 
| 4 | 
            -
            import shutil
         | 
| 5 3 | 
             
            import typing as ta
         | 
| 6 4 |  | 
| 7 5 | 
             
            from omlish.lite.cached import cached_nullary
         | 
| @@ -12,28 +10,12 @@ from .conf import DeployConfManager | |
| 12 10 | 
             
            from .git import DeployGitManager
         | 
| 13 11 | 
             
            from .paths.owners import DeployPathOwner
         | 
| 14 12 | 
             
            from .paths.paths import DeployPath
         | 
| 15 | 
            -
            from .specs import  | 
| 16 | 
            -
            from . | 
| 13 | 
            +
            from .specs import DeployAppSpec
         | 
| 14 | 
            +
            from .tags import DeployTagMap
         | 
| 17 15 | 
             
            from .types import DeployHome
         | 
| 18 | 
            -
            from .types import DeployKey
         | 
| 19 | 
            -
            from .types import DeployRev
         | 
| 20 | 
            -
            from .types import DeployTag
         | 
| 21 16 | 
             
            from .venvs import DeployVenvManager
         | 
| 22 17 |  | 
| 23 18 |  | 
| 24 | 
            -
            def make_deploy_tag(
         | 
| 25 | 
            -
                    rev: DeployRev,
         | 
| 26 | 
            -
                    key: DeployKey,
         | 
| 27 | 
            -
                    *,
         | 
| 28 | 
            -
                    utcnow: ta.Optional[datetime.datetime] = None,
         | 
| 29 | 
            -
            ) -> DeployTag:
         | 
| 30 | 
            -
                if utcnow is None:
         | 
| 31 | 
            -
                    utcnow = datetime.datetime.now(tz=datetime.timezone.utc)  # noqa
         | 
| 32 | 
            -
                now_fmt = '%Y%m%dT%H%M%SZ'
         | 
| 33 | 
            -
                now_str = utcnow.strftime(now_fmt)
         | 
| 34 | 
            -
                return DeployTag('-'.join([now_str, rev, key]))
         | 
| 35 | 
            -
             | 
| 36 | 
            -
             | 
| 37 19 | 
             
            class DeployAppManager(DeployPathOwner):
         | 
| 38 20 | 
             
                def __init__(
         | 
| 39 21 | 
             
                        self,
         | 
| @@ -54,32 +36,27 @@ class DeployAppManager(DeployPathOwner): | |
| 54 36 |  | 
| 55 37 | 
             
                #
         | 
| 56 38 |  | 
| 57 | 
            -
                 | 
| 58 | 
            -
                 | 
| 39 | 
            +
                _APP_DIR_STR = 'apps/@app/@time--@app-rev--@app-key/'
         | 
| 40 | 
            +
                _APP_DIR = DeployPath.parse(_APP_DIR_STR)
         | 
| 59 41 |  | 
| 60 | 
            -
                 | 
| 61 | 
            -
                _CONF_TAG_DIR = DeployPath.parse(_CONF_TAG_DIR_STR)
         | 
| 62 | 
            -
             | 
| 63 | 
            -
                _DEPLOY_DIR_STR = 'deploys/@tag--@app/'
         | 
| 42 | 
            +
                _DEPLOY_DIR_STR = 'deploys/@time--@deploy-key/'
         | 
| 64 43 | 
             
                _DEPLOY_DIR = DeployPath.parse(_DEPLOY_DIR_STR)
         | 
| 65 44 |  | 
| 66 45 | 
             
                _APP_DEPLOY_LINK = DeployPath.parse(f'{_DEPLOY_DIR_STR}apps/@app')
         | 
| 67 | 
            -
                 | 
| 46 | 
            +
                _CONF_DEPLOY_DIR = DeployPath.parse(f'{_DEPLOY_DIR_STR}conf/@conf/')
         | 
| 68 47 |  | 
| 69 48 | 
             
                @cached_nullary
         | 
| 70 49 | 
             
                def get_owned_deploy_paths(self) -> ta.AbstractSet[DeployPath]:
         | 
| 71 50 | 
             
                    return {
         | 
| 72 | 
            -
                        self. | 
| 73 | 
            -
             | 
| 74 | 
            -
                        self._CONF_TAG_DIR,
         | 
| 51 | 
            +
                        self._APP_DIR,
         | 
| 75 52 |  | 
| 76 53 | 
             
                        self._DEPLOY_DIR,
         | 
| 77 54 |  | 
| 78 55 | 
             
                        self._APP_DEPLOY_LINK,
         | 
| 79 | 
            -
                        self. | 
| 56 | 
            +
                        self._CONF_DEPLOY_DIR,
         | 
| 80 57 |  | 
| 81 58 | 
             
                        *[
         | 
| 82 | 
            -
                            DeployPath.parse(f'{self. | 
| 59 | 
            +
                            DeployPath.parse(f'{self._APP_DIR_STR}{sfx}/')
         | 
| 83 60 | 
             
                            for sfx in [
         | 
| 84 61 | 
             
                                'conf',
         | 
| 85 62 | 
             
                                'git',
         | 
| @@ -92,26 +69,21 @@ class DeployAppManager(DeployPathOwner): | |
| 92 69 |  | 
| 93 70 | 
             
                async def prepare_app(
         | 
| 94 71 | 
             
                        self,
         | 
| 95 | 
            -
                        spec:  | 
| 72 | 
            +
                        spec: DeployAppSpec,
         | 
| 73 | 
            +
                        tags: DeployTagMap,
         | 
| 96 74 | 
             
                ) -> None:
         | 
| 97 | 
            -
                    app_tag = DeployAppTag(spec.app, make_deploy_tag(spec.git.rev, spec.key()))
         | 
| 98 | 
            -
             | 
| 99 | 
            -
                    #
         | 
| 100 | 
            -
             | 
| 101 75 | 
             
                    deploy_home = check.non_empty_str(self._deploy_home)
         | 
| 102 76 |  | 
| 103 77 | 
             
                    def build_path(pth: DeployPath) -> str:
         | 
| 104 | 
            -
                        return os.path.join(deploy_home, pth.render( | 
| 78 | 
            +
                        return os.path.join(deploy_home, pth.render(tags))
         | 
| 105 79 |  | 
| 106 | 
            -
                     | 
| 107 | 
            -
                    conf_tag_dir = build_path(self._CONF_TAG_DIR)
         | 
| 80 | 
            +
                    app_dir = build_path(self._APP_DIR)
         | 
| 108 81 | 
             
                    deploy_dir = build_path(self._DEPLOY_DIR)
         | 
| 109 82 | 
             
                    app_deploy_link = build_path(self._APP_DEPLOY_LINK)
         | 
| 110 | 
            -
                    conf_deploy_link_file = build_path(self._CONF_DEPLOY_LINK)
         | 
| 111 83 |  | 
| 112 84 | 
             
                    #
         | 
| 113 85 |  | 
| 114 | 
            -
                    os.makedirs(deploy_dir)
         | 
| 86 | 
            +
                    os.makedirs(deploy_dir, exist_ok=True)
         | 
| 115 87 |  | 
| 116 88 | 
             
                    deploying_link = os.path.join(deploy_home, 'deploys/deploying')
         | 
| 117 89 | 
             
                    relative_symlink(
         | 
| @@ -123,9 +95,9 @@ class DeployAppManager(DeployPathOwner): | |
| 123 95 |  | 
| 124 96 | 
             
                    #
         | 
| 125 97 |  | 
| 126 | 
            -
                    os.makedirs( | 
| 98 | 
            +
                    os.makedirs(app_dir)
         | 
| 127 99 | 
             
                    relative_symlink(
         | 
| 128 | 
            -
                         | 
| 100 | 
            +
                        app_dir,
         | 
| 129 101 | 
             
                        app_deploy_link,
         | 
| 130 102 | 
             
                        target_is_directory=True,
         | 
| 131 103 | 
             
                        make_dirs=True,
         | 
| @@ -133,37 +105,33 @@ class DeployAppManager(DeployPathOwner): | |
| 133 105 |  | 
| 134 106 | 
             
                    #
         | 
| 135 107 |  | 
| 136 | 
            -
                    os. | 
| 137 | 
            -
                     | 
| 138 | 
            -
                        conf_tag_dir,
         | 
| 139 | 
            -
                        conf_deploy_link_file,
         | 
| 140 | 
            -
                        target_is_directory=True,
         | 
| 141 | 
            -
                        make_dirs=True,
         | 
| 142 | 
            -
                    )
         | 
| 108 | 
            +
                    deploy_conf_dir = os.path.join(deploy_dir, 'conf')
         | 
| 109 | 
            +
                    os.makedirs(deploy_conf_dir, exist_ok=True)
         | 
| 143 110 |  | 
| 144 111 | 
             
                    #
         | 
| 145 112 |  | 
| 146 | 
            -
                    def mirror_symlinks(src: str, dst: str) -> None:
         | 
| 147 | 
            -
             | 
| 148 | 
            -
             | 
| 149 | 
            -
             | 
| 150 | 
            -
             | 
| 151 | 
            -
             | 
| 152 | 
            -
             | 
| 153 | 
            -
             | 
| 154 | 
            -
             | 
| 155 | 
            -
             | 
| 156 | 
            -
             | 
| 157 | 
            -
             | 
| 158 | 
            -
             | 
| 159 | 
            -
             | 
| 160 | 
            -
             | 
| 161 | 
            -
             | 
| 162 | 
            -
             | 
| 163 | 
            -
             | 
| 164 | 
            -
             | 
| 113 | 
            +
                    # def mirror_symlinks(src: str, dst: str) -> None:
         | 
| 114 | 
            +
                    #     def mirror_link(lp: str) -> None:
         | 
| 115 | 
            +
                    #         check.state(os.path.islink(lp))
         | 
| 116 | 
            +
                    #         shutil.copy2(
         | 
| 117 | 
            +
                    #             lp,
         | 
| 118 | 
            +
                    #             os.path.join(dst, os.path.relpath(lp, src)),
         | 
| 119 | 
            +
                    #             follow_symlinks=False,
         | 
| 120 | 
            +
                    #         )
         | 
| 121 | 
            +
                    #
         | 
| 122 | 
            +
                    #     for dp, dns, fns in os.walk(src, followlinks=False):
         | 
| 123 | 
            +
                    #         for fn in fns:
         | 
| 124 | 
            +
                    #             mirror_link(os.path.join(dp, fn))
         | 
| 125 | 
            +
                    #
         | 
| 126 | 
            +
                    #         for dn in dns:
         | 
| 127 | 
            +
                    #             dp2 = os.path.join(dp, dn)
         | 
| 128 | 
            +
                    #             if os.path.islink(dp2):
         | 
| 129 | 
            +
                    #                 mirror_link(dp2)
         | 
| 130 | 
            +
                    #             else:
         | 
| 131 | 
            +
                    #                 os.makedirs(os.path.join(dst, os.path.relpath(dp2, src)))
         | 
| 165 132 |  | 
| 166 133 | 
             
                    current_link = os.path.join(deploy_home, 'deploys/current')
         | 
| 134 | 
            +
             | 
| 167 135 | 
             
                    # if os.path.exists(current_link):
         | 
| 168 136 | 
             
                    #     mirror_symlinks(
         | 
| 169 137 | 
             
                    #         os.path.join(current_link, 'conf'),
         | 
| @@ -176,31 +144,31 @@ class DeployAppManager(DeployPathOwner): | |
| 176 144 |  | 
| 177 145 | 
             
                    #
         | 
| 178 146 |  | 
| 179 | 
            -
                     | 
| 147 | 
            +
                    app_git_dir = os.path.join(app_dir, 'git')
         | 
| 180 148 | 
             
                    await self._git.checkout(
         | 
| 181 149 | 
             
                        spec.git,
         | 
| 182 | 
            -
                         | 
| 150 | 
            +
                        app_git_dir,
         | 
| 183 151 | 
             
                    )
         | 
| 184 152 |  | 
| 185 153 | 
             
                    #
         | 
| 186 154 |  | 
| 187 155 | 
             
                    if spec.venv is not None:
         | 
| 188 | 
            -
                         | 
| 156 | 
            +
                        app_venv_dir = os.path.join(app_dir, 'venv')
         | 
| 189 157 | 
             
                        await self._venvs.setup_venv(
         | 
| 190 158 | 
             
                            spec.venv,
         | 
| 191 | 
            -
                             | 
| 192 | 
            -
                             | 
| 159 | 
            +
                            app_git_dir,
         | 
| 160 | 
            +
                            app_venv_dir,
         | 
| 193 161 | 
             
                        )
         | 
| 194 162 |  | 
| 195 163 | 
             
                    #
         | 
| 196 164 |  | 
| 197 165 | 
             
                    if spec.conf is not None:
         | 
| 198 | 
            -
                         | 
| 199 | 
            -
                        await self._conf. | 
| 166 | 
            +
                        app_conf_dir = os.path.join(app_dir, 'conf')
         | 
| 167 | 
            +
                        await self._conf.write_app_conf(
         | 
| 200 168 | 
             
                            spec.conf,
         | 
| 201 | 
            -
                             | 
| 202 | 
            -
                             | 
| 203 | 
            -
                             | 
| 169 | 
            +
                            tags,
         | 
| 170 | 
            +
                            app_conf_dir,
         | 
| 171 | 
            +
                            deploy_conf_dir,
         | 
| 204 172 | 
             
                        )
         | 
| 205 173 |  | 
| 206 174 | 
             
                    #
         | 
    
        ominfra/manage/deploy/conf.py
    CHANGED
    
    | @@ -23,13 +23,15 @@ from omlish.lite.check import check | |
| 23 23 | 
             
            from omlish.os.paths import is_path_in_dir
         | 
| 24 24 | 
             
            from omlish.os.paths import relative_symlink
         | 
| 25 25 |  | 
| 26 | 
            -
            from .paths.paths import  | 
| 27 | 
            -
            from .specs import  | 
| 28 | 
            -
            from .specs import  | 
| 29 | 
            -
            from .specs import  | 
| 30 | 
            -
            from .specs import  | 
| 31 | 
            -
            from .specs import  | 
| 32 | 
            -
            from . | 
| 26 | 
            +
            from .paths.paths import DeployPath
         | 
| 27 | 
            +
            from .specs import AllActiveDeployAppConfLink
         | 
| 28 | 
            +
            from .specs import CurrentOnlyDeployAppConfLink
         | 
| 29 | 
            +
            from .specs import DeployAppConfFile
         | 
| 30 | 
            +
            from .specs import DeployAppConfLink
         | 
| 31 | 
            +
            from .specs import DeployAppConfSpec
         | 
| 32 | 
            +
            from .tags import DEPLOY_TAG_SEPARATOR
         | 
| 33 | 
            +
            from .tags import DeployApp
         | 
| 34 | 
            +
            from .tags import DeployTagMap
         | 
| 33 35 | 
             
            from .types import DeployHome
         | 
| 34 36 |  | 
| 35 37 |  | 
| @@ -45,18 +47,18 @@ class DeployConfManager: | |
| 45 47 |  | 
| 46 48 | 
             
                #
         | 
| 47 49 |  | 
| 48 | 
            -
                async def  | 
| 50 | 
            +
                async def _write_app_conf_file(
         | 
| 49 51 | 
             
                        self,
         | 
| 50 | 
            -
                         | 
| 51 | 
            -
                         | 
| 52 | 
            +
                        acf: DeployAppConfFile,
         | 
| 53 | 
            +
                        app_conf_dir: str,
         | 
| 52 54 | 
             
                ) -> None:
         | 
| 53 | 
            -
                    conf_file = os.path.join( | 
| 54 | 
            -
                    check.arg(is_path_in_dir( | 
| 55 | 
            +
                    conf_file = os.path.join(app_conf_dir, acf.path)
         | 
| 56 | 
            +
                    check.arg(is_path_in_dir(app_conf_dir, conf_file))
         | 
| 55 57 |  | 
| 56 58 | 
             
                    os.makedirs(os.path.dirname(conf_file), exist_ok=True)
         | 
| 57 59 |  | 
| 58 60 | 
             
                    with open(conf_file, 'w') as f:  # noqa
         | 
| 59 | 
            -
                        f.write( | 
| 61 | 
            +
                        f.write(acf.body)
         | 
| 60 62 |  | 
| 61 63 | 
             
                #
         | 
| 62 64 |  | 
| @@ -65,15 +67,18 @@ class DeployConfManager: | |
| 65 67 | 
             
                    link_src: str
         | 
| 66 68 | 
             
                    link_dst: str
         | 
| 67 69 |  | 
| 68 | 
            -
                 | 
| 70 | 
            +
                _UNIQUE_LINK_NAME_STR = '@app--@time--@app-key'
         | 
| 71 | 
            +
                _UNIQUE_LINK_NAME = DeployPath.parse(_UNIQUE_LINK_NAME_STR)
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                def _compute_app_conf_link_dst(
         | 
| 69 74 | 
             
                        self,
         | 
| 70 | 
            -
                        link:  | 
| 71 | 
            -
                         | 
| 72 | 
            -
                         | 
| 73 | 
            -
                         | 
| 75 | 
            +
                        link: DeployAppConfLink,
         | 
| 76 | 
            +
                        tags: DeployTagMap,
         | 
| 77 | 
            +
                        app_conf_dir: str,
         | 
| 78 | 
            +
                        conf_link_dir: str,
         | 
| 74 79 | 
             
                ) -> _ComputedConfLink:
         | 
| 75 | 
            -
                    link_src = os.path.join( | 
| 76 | 
            -
                    check.arg(is_path_in_dir( | 
| 80 | 
            +
                    link_src = os.path.join(app_conf_dir, link.src)
         | 
| 81 | 
            +
                    check.arg(is_path_in_dir(app_conf_dir, link_src))
         | 
| 77 82 |  | 
| 78 83 | 
             
                    #
         | 
| 79 84 |  | 
| @@ -88,7 +93,7 @@ class DeployConfManager: | |
| 88 93 | 
             
                        d, f = os.path.split(link.src)
         | 
| 89 94 | 
             
                        # TODO: check filename :|
         | 
| 90 95 | 
             
                        link_dst_pfx = d + '/'
         | 
| 91 | 
            -
                        link_dst_sfx =  | 
| 96 | 
            +
                        link_dst_sfx = DEPLOY_TAG_SEPARATOR + f
         | 
| 92 97 |  | 
| 93 98 | 
             
                    else:  # noqa
         | 
| 94 99 | 
             
                        # @conf(.ext)* - links a single file in root of app conf dir to conf/@conf/@dst(.ext)*
         | 
| @@ -102,10 +107,10 @@ class DeployConfManager: | |
| 102 107 |  | 
| 103 108 | 
             
                    #
         | 
| 104 109 |  | 
| 105 | 
            -
                    if isinstance(link,  | 
| 106 | 
            -
                        link_dst_mid = str( | 
| 107 | 
            -
                    elif isinstance(link,  | 
| 108 | 
            -
                        link_dst_mid =  | 
| 110 | 
            +
                    if isinstance(link, CurrentOnlyDeployAppConfLink):
         | 
| 111 | 
            +
                        link_dst_mid = str(tags[DeployApp].s)
         | 
| 112 | 
            +
                    elif isinstance(link, AllActiveDeployAppConfLink):
         | 
| 113 | 
            +
                        link_dst_mid = self._UNIQUE_LINK_NAME.render(tags)
         | 
| 109 114 | 
             
                    else:
         | 
| 110 115 | 
             
                        raise TypeError(link)
         | 
| 111 116 |  | 
| @@ -116,7 +121,7 @@ class DeployConfManager: | |
| 116 121 | 
             
                        link_dst_mid,
         | 
| 117 122 | 
             
                        link_dst_sfx,
         | 
| 118 123 | 
             
                    ])
         | 
| 119 | 
            -
                    link_dst = os.path.join( | 
| 124 | 
            +
                    link_dst = os.path.join(conf_link_dir, link_dst_name)
         | 
| 120 125 |  | 
| 121 126 | 
             
                    return DeployConfManager._ComputedConfLink(
         | 
| 122 127 | 
             
                        is_dir=is_dir,
         | 
| @@ -124,24 +129,24 @@ class DeployConfManager: | |
| 124 129 | 
             
                        link_dst=link_dst,
         | 
| 125 130 | 
             
                    )
         | 
| 126 131 |  | 
| 127 | 
            -
                async def  | 
| 132 | 
            +
                async def _make_app_conf_link(
         | 
| 128 133 | 
             
                        self,
         | 
| 129 | 
            -
                        link:  | 
| 130 | 
            -
                         | 
| 131 | 
            -
                         | 
| 132 | 
            -
                         | 
| 134 | 
            +
                        link: DeployAppConfLink,
         | 
| 135 | 
            +
                        tags: DeployTagMap,
         | 
| 136 | 
            +
                        app_conf_dir: str,
         | 
| 137 | 
            +
                        conf_link_dir: str,
         | 
| 133 138 | 
             
                ) -> None:
         | 
| 134 | 
            -
                    comp = self. | 
| 139 | 
            +
                    comp = self._compute_app_conf_link_dst(
         | 
| 135 140 | 
             
                        link,
         | 
| 136 | 
            -
                         | 
| 137 | 
            -
                         | 
| 138 | 
            -
                         | 
| 141 | 
            +
                        tags,
         | 
| 142 | 
            +
                        app_conf_dir,
         | 
| 143 | 
            +
                        conf_link_dir,
         | 
| 139 144 | 
             
                    )
         | 
| 140 145 |  | 
| 141 146 | 
             
                    #
         | 
| 142 147 |  | 
| 143 | 
            -
                    check.arg(is_path_in_dir( | 
| 144 | 
            -
                    check.arg(is_path_in_dir( | 
| 148 | 
            +
                    check.arg(is_path_in_dir(app_conf_dir, comp.link_src))
         | 
| 149 | 
            +
                    check.arg(is_path_in_dir(conf_link_dir, comp.link_dst))
         | 
| 145 150 |  | 
| 146 151 | 
             
                    if comp.is_dir:
         | 
| 147 152 | 
             
                        check.arg(os.path.isdir(comp.link_src))
         | 
| @@ -159,25 +164,25 @@ class DeployConfManager: | |
| 159 164 |  | 
| 160 165 | 
             
                #
         | 
| 161 166 |  | 
| 162 | 
            -
                async def  | 
| 167 | 
            +
                async def write_app_conf(
         | 
| 163 168 | 
             
                        self,
         | 
| 164 | 
            -
                        spec:  | 
| 165 | 
            -
                         | 
| 166 | 
            -
                         | 
| 167 | 
            -
                         | 
| 169 | 
            +
                        spec: DeployAppConfSpec,
         | 
| 170 | 
            +
                        tags: DeployTagMap,
         | 
| 171 | 
            +
                        app_conf_dir: str,
         | 
| 172 | 
            +
                        conf_link_dir: str,
         | 
| 168 173 | 
             
                ) -> None:
         | 
| 169 | 
            -
                    for  | 
| 170 | 
            -
                        await self. | 
| 171 | 
            -
                             | 
| 172 | 
            -
                             | 
| 174 | 
            +
                    for acf in spec.files or []:
         | 
| 175 | 
            +
                        await self._write_app_conf_file(
         | 
| 176 | 
            +
                            acf,
         | 
| 177 | 
            +
                            app_conf_dir,
         | 
| 173 178 | 
             
                        )
         | 
| 174 179 |  | 
| 175 180 | 
             
                    #
         | 
| 176 181 |  | 
| 177 182 | 
             
                    for link in spec.links or []:
         | 
| 178 | 
            -
                        await self. | 
| 183 | 
            +
                        await self._make_app_conf_link(
         | 
| 179 184 | 
             
                            link,
         | 
| 180 | 
            -
                             | 
| 181 | 
            -
                             | 
| 182 | 
            -
                             | 
| 185 | 
            +
                            tags,
         | 
| 186 | 
            +
                            app_conf_dir,
         | 
| 187 | 
            +
                            conf_link_dir,
         | 
| 183 188 | 
             
                        )
         | 
    
        ominfra/manage/deploy/deploy.py
    CHANGED
    
    | @@ -1,7 +1,21 @@ | |
| 1 1 | 
             
            # ruff: noqa: UP006 UP007
         | 
| 2 | 
            +
            import datetime
         | 
| 3 | 
            +
            import typing as ta
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            from omlish.lite.typing import Func0
         | 
| 6 | 
            +
             | 
| 2 7 | 
             
            from .apps import DeployAppManager
         | 
| 3 8 | 
             
            from .paths.manager import DeployPathsManager
         | 
| 4 9 | 
             
            from .specs import DeploySpec
         | 
| 10 | 
            +
            from .tags import DeployAppRev
         | 
| 11 | 
            +
            from .tags import DeployTagMap
         | 
| 12 | 
            +
            from .tags import DeployTime
         | 
| 13 | 
            +
             | 
| 14 | 
            +
             | 
| 15 | 
            +
            DEPLOY_TAG_DATETIME_FMT = '%Y%m%dT%H%M%SZ'
         | 
| 16 | 
            +
             | 
| 17 | 
            +
             | 
| 18 | 
            +
            DeployManagerUtcClock = ta.NewType('DeployManagerUtcClock', Func0[datetime.datetime])
         | 
| 5 19 |  | 
| 6 20 |  | 
| 7 21 | 
             
            class DeployManager:
         | 
| @@ -10,12 +24,25 @@ class DeployManager: | |
| 10 24 | 
             
                        *,
         | 
| 11 25 | 
             
                        apps: DeployAppManager,
         | 
| 12 26 | 
             
                        paths: DeployPathsManager,
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                        utc_clock: ta.Optional[DeployManagerUtcClock] = None,
         | 
| 13 29 | 
             
                ):
         | 
| 14 30 | 
             
                    super().__init__()
         | 
| 15 31 |  | 
| 16 32 | 
             
                    self._apps = apps
         | 
| 17 33 | 
             
                    self._paths = paths
         | 
| 18 34 |  | 
| 35 | 
            +
                    self._utc_clock = utc_clock
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                def _utc_now(self) -> datetime.datetime:
         | 
| 38 | 
            +
                    if self._utc_clock is not None:
         | 
| 39 | 
            +
                        return self._utc_clock()  # noqa
         | 
| 40 | 
            +
                    else:
         | 
| 41 | 
            +
                        return datetime.datetime.now(tz=datetime.timezone.utc)  # noqa
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                def _make_deploy_time(self) -> DeployTime:
         | 
| 44 | 
            +
                    return DeployTime(self._utc_now().strftime(DEPLOY_TAG_DATETIME_FMT))
         | 
| 45 | 
            +
             | 
| 19 46 | 
             
                async def run_deploy(
         | 
| 20 47 | 
             
                        self,
         | 
| 21 48 | 
             
                        spec: DeploySpec,
         | 
| @@ -24,4 +51,21 @@ class DeployManager: | |
| 24 51 |  | 
| 25 52 | 
             
                    #
         | 
| 26 53 |  | 
| 27 | 
            -
                     | 
| 54 | 
            +
                    deploy_tags = DeployTagMap(
         | 
| 55 | 
            +
                        self._make_deploy_time(),
         | 
| 56 | 
            +
                        spec.key(),
         | 
| 57 | 
            +
                    )
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                    #
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                    for app in spec.apps:
         | 
| 62 | 
            +
                        app_tags = deploy_tags.add(
         | 
| 63 | 
            +
                            app.app,
         | 
| 64 | 
            +
                            app.key(),
         | 
| 65 | 
            +
                            DeployAppRev(app.git.rev),
         | 
| 66 | 
            +
                        )
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                        await self._apps.prepare_app(
         | 
| 69 | 
            +
                            app,
         | 
| 70 | 
            +
                            app_tags,
         | 
| 71 | 
            +
                        )
         | 
| @@ -13,38 +13,28 @@ import dataclasses as dc | |
| 13 13 | 
             
            import itertools
         | 
| 14 14 | 
             
            import typing as ta
         | 
| 15 15 |  | 
| 16 | 
            +
            from omlish.lite.cached import cached_nullary
         | 
| 16 17 | 
             
            from omlish.lite.check import check
         | 
| 17 18 | 
             
            from omlish.lite.strings import split_keep_delimiter
         | 
| 18 19 |  | 
| 19 | 
            -
            from .. | 
| 20 | 
            -
            from .. | 
| 20 | 
            +
            from ..tags import DEPLOY_TAG_DELIMITERS
         | 
| 21 | 
            +
            from ..tags import DEPLOY_TAG_SIGIL
         | 
| 22 | 
            +
            from ..tags import DEPLOY_TAGS_BY_NAME
         | 
| 23 | 
            +
            from ..tags import DeployTag
         | 
| 24 | 
            +
            from ..tags import DeployTagMap
         | 
| 25 | 
            +
            from .types import DeployPathKind
         | 
| 21 26 |  | 
| 22 27 |  | 
| 23 28 | 
             
            ##
         | 
| 24 29 |  | 
| 25 30 |  | 
| 26 | 
            -
            DEPLOY_PATH_PLACEHOLDER_SIGIL = '@'
         | 
| 27 | 
            -
            DEPLOY_PATH_PLACEHOLDER_SEPARATOR = '--'
         | 
| 28 | 
            -
             | 
| 29 | 
            -
            DEPLOY_PATH_PLACEHOLDER_DELIMITERS: ta.AbstractSet[str] = frozenset([
         | 
| 30 | 
            -
                DEPLOY_PATH_PLACEHOLDER_SEPARATOR,
         | 
| 31 | 
            -
                '.',
         | 
| 32 | 
            -
            ])
         | 
| 33 | 
            -
             | 
| 34 | 
            -
            DEPLOY_PATH_PLACEHOLDERS: ta.FrozenSet[str] = frozenset([
         | 
| 35 | 
            -
                'app',
         | 
| 36 | 
            -
                'tag',
         | 
| 37 | 
            -
                'conf',
         | 
| 38 | 
            -
            ])
         | 
| 39 | 
            -
             | 
| 40 | 
            -
             | 
| 41 31 | 
             
            class DeployPathError(Exception):
         | 
| 42 32 | 
             
                pass
         | 
| 43 33 |  | 
| 44 34 |  | 
| 45 35 | 
             
            class DeployPathRenderable(abc.ABC):
         | 
| 46 36 | 
             
                @abc.abstractmethod
         | 
| 47 | 
            -
                def render(self,  | 
| 37 | 
            +
                def render(self, tags: ta.Optional[DeployTagMap] = None) -> str:
         | 
| 48 38 | 
             
                    raise NotImplementedError
         | 
| 49 39 |  | 
| 50 40 |  | 
| @@ -55,26 +45,30 @@ class DeployPathNamePart(DeployPathRenderable, abc.ABC): | |
| 55 45 | 
             
                @classmethod
         | 
| 56 46 | 
             
                def parse(cls, s: str) -> 'DeployPathNamePart':
         | 
| 57 47 | 
             
                    check.non_empty_str(s)
         | 
| 58 | 
            -
                    if s.startswith( | 
| 59 | 
            -
                        return  | 
| 60 | 
            -
                    elif s in  | 
| 48 | 
            +
                    if s.startswith(DEPLOY_TAG_SIGIL):
         | 
| 49 | 
            +
                        return TagDeployPathNamePart(s[1:])
         | 
| 50 | 
            +
                    elif s in DEPLOY_TAG_DELIMITERS:
         | 
| 61 51 | 
             
                        return DelimiterDeployPathNamePart(s)
         | 
| 62 52 | 
             
                    else:
         | 
| 63 53 | 
             
                        return ConstDeployPathNamePart(s)
         | 
| 64 54 |  | 
| 65 55 |  | 
| 66 56 | 
             
            @dc.dataclass(frozen=True)
         | 
| 67 | 
            -
            class  | 
| 68 | 
            -
                 | 
| 57 | 
            +
            class TagDeployPathNamePart(DeployPathNamePart):
         | 
| 58 | 
            +
                name: str
         | 
| 69 59 |  | 
| 70 60 | 
             
                def __post_init__(self) -> None:
         | 
| 71 | 
            -
                    check.in_(self. | 
| 61 | 
            +
                    check.in_(self.name, DEPLOY_TAGS_BY_NAME)
         | 
| 72 62 |  | 
| 73 | 
            -
                 | 
| 74 | 
            -
             | 
| 75 | 
            -
             | 
| 63 | 
            +
                @property
         | 
| 64 | 
            +
                def tag(self) -> ta.Type[DeployTag]:
         | 
| 65 | 
            +
                    return DEPLOY_TAGS_BY_NAME[self.name]
         | 
| 66 | 
            +
             | 
| 67 | 
            +
                def render(self, tags: ta.Optional[DeployTagMap] = None) -> str:
         | 
| 68 | 
            +
                    if tags is not None:
         | 
| 69 | 
            +
                        return tags[self.tag].s
         | 
| 76 70 | 
             
                    else:
         | 
| 77 | 
            -
                        return  | 
| 71 | 
            +
                        return DEPLOY_TAG_SIGIL + self.name
         | 
| 78 72 |  | 
| 79 73 |  | 
| 80 74 | 
             
            @dc.dataclass(frozen=True)
         | 
| @@ -82,9 +76,9 @@ class DelimiterDeployPathNamePart(DeployPathNamePart): | |
| 82 76 | 
             
                delimiter: str
         | 
| 83 77 |  | 
| 84 78 | 
             
                def __post_init__(self) -> None:
         | 
| 85 | 
            -
                    check.in_(self.delimiter,  | 
| 79 | 
            +
                    check.in_(self.delimiter, DEPLOY_TAG_DELIMITERS)
         | 
| 86 80 |  | 
| 87 | 
            -
                def render(self,  | 
| 81 | 
            +
                def render(self, tags: ta.Optional[DeployTagMap] = None) -> str:
         | 
| 88 82 | 
             
                    return self.delimiter
         | 
| 89 83 |  | 
| 90 84 |  | 
| @@ -94,10 +88,10 @@ class ConstDeployPathNamePart(DeployPathNamePart): | |
| 94 88 |  | 
| 95 89 | 
             
                def __post_init__(self) -> None:
         | 
| 96 90 | 
             
                    check.non_empty_str(self.const)
         | 
| 97 | 
            -
                    for c in [* | 
| 91 | 
            +
                    for c in [*DEPLOY_TAG_DELIMITERS, DEPLOY_TAG_SIGIL, '/']:
         | 
| 98 92 | 
             
                        check.not_in(c, self.const)
         | 
| 99 93 |  | 
| 100 | 
            -
                def render(self,  | 
| 94 | 
            +
                def render(self, tags: ta.Optional[DeployTagMap] = None) -> str:
         | 
| 101 95 | 
             
                    return self.const
         | 
| 102 96 |  | 
| 103 97 |  | 
| @@ -112,8 +106,8 @@ class DeployPathName(DeployPathRenderable): | |
| 112 106 | 
             
                        if len(gl := list(g)) > 1:
         | 
| 113 107 | 
             
                            raise DeployPathError(f'May not have consecutive path name part types: {k} {gl}')
         | 
| 114 108 |  | 
| 115 | 
            -
                def render(self,  | 
| 116 | 
            -
                    return ''.join(p.render( | 
| 109 | 
            +
                def render(self, tags: ta.Optional[DeployTagMap] = None) -> str:
         | 
| 110 | 
            +
                    return ''.join(p.render(tags) for p in self.parts)
         | 
| 117 111 |  | 
| 118 112 | 
             
                @classmethod
         | 
| 119 113 | 
             
                def parse(cls, s: str) -> 'DeployPathName':
         | 
| @@ -123,7 +117,7 @@ class DeployPathName(DeployPathRenderable): | |
| 123 117 | 
             
                    i = 0
         | 
| 124 118 | 
             
                    ps = []
         | 
| 125 119 | 
             
                    while i < len(s):
         | 
| 126 | 
            -
                        ns = [(n, d) for d in  | 
| 120 | 
            +
                        ns = [(n, d) for d in DEPLOY_TAG_DELIMITERS if (n := s.find(d, i)) >= 0]
         | 
| 127 121 | 
             
                        if not ns:
         | 
| 128 122 | 
             
                            ps.append(s[i:])
         | 
| 129 123 | 
             
                            break
         | 
| @@ -147,8 +141,8 @@ class DeployPathPart(DeployPathRenderable, abc.ABC):  # noqa | |
| 147 141 | 
             
                def kind(self) -> DeployPathKind:
         | 
| 148 142 | 
             
                    raise NotImplementedError
         | 
| 149 143 |  | 
| 150 | 
            -
                def render(self,  | 
| 151 | 
            -
                    return self.name.render( | 
| 144 | 
            +
                def render(self, tags: ta.Optional[DeployTagMap] = None) -> str:
         | 
| 145 | 
            +
                    return self.name.render(tags) + ('/' if self.kind == 'dir' else '')
         | 
| 152 146 |  | 
| 153 147 | 
             
                @classmethod
         | 
| 154 148 | 
             
                def parse(cls, s: str) -> 'DeployPathPart':
         | 
| @@ -194,20 +188,20 @@ class DeployPath: | |
| 194 188 | 
             
                    for p in self.parts[:-1]:
         | 
| 195 189 | 
             
                        check.equal(p.kind, 'dir')
         | 
| 196 190 |  | 
| 197 | 
            -
             | 
| 191 | 
            +
                @cached_nullary
         | 
| 192 | 
            +
                def tag_indices(self) -> ta.Mapping[ta.Type[DeployTag], ta.Sequence[int]]:
         | 
| 193 | 
            +
                    pd: ta.Dict[ta.Type[DeployTag], ta.List[int]] = {}
         | 
| 198 194 | 
             
                    for i, np in enumerate(self.name_parts):
         | 
| 199 | 
            -
                        if isinstance(np,  | 
| 200 | 
            -
                            pd.setdefault( | 
| 201 | 
            -
             | 
| 202 | 
            -
                    # if 'tag' in pd and 'app' not in pd:
         | 
| 203 | 
            -
                    #     raise DeployPathError('Tag placeholder in path without app', self)
         | 
| 195 | 
            +
                        if isinstance(np, TagDeployPathNamePart):
         | 
| 196 | 
            +
                            pd.setdefault(np.tag, []).append(i)
         | 
| 197 | 
            +
                    return pd
         | 
| 204 198 |  | 
| 205 199 | 
             
                @property
         | 
| 206 200 | 
             
                def kind(self) -> ta.Literal['file', 'dir']:
         | 
| 207 201 | 
             
                    return self.parts[-1].kind
         | 
| 208 202 |  | 
| 209 | 
            -
                def render(self,  | 
| 210 | 
            -
                    return ''.join([p.render( | 
| 203 | 
            +
                def render(self, tags: ta.Optional[DeployTagMap] = None) -> str:
         | 
| 204 | 
            +
                    return ''.join([p.render(tags) for p in self.parts])
         | 
| 211 205 |  | 
| 212 206 | 
             
                @classmethod
         | 
| 213 207 | 
             
                def parse(cls, s: str) -> 'DeployPath':
         |