ominfra 0.0.0.dev147__py3-none-any.whl → 0.0.0.dev148__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/configs.py +30 -5
 - ominfra/manage/commands/base.py +5 -5
 - ominfra/manage/commands/execution.py +2 -2
 - ominfra/manage/commands/inject.py +1 -1
 - ominfra/manage/commands/interp.py +2 -2
 - ominfra/manage/commands/subprocess.py +22 -14
 - ominfra/manage/deploy/command.py +1 -1
 - ominfra/manage/main.py +48 -32
 - ominfra/manage/remote/_main.py +172 -0
 - ominfra/manage/remote/channel.py +41 -16
 - ominfra/manage/remote/config.py +10 -0
 - ominfra/manage/remote/connection.py +106 -0
 - ominfra/manage/remote/execution.py +244 -155
 - ominfra/manage/remote/inject.py +7 -3
 - ominfra/manage/remote/spawning.py +51 -33
 - ominfra/pyremote.py +28 -3
 - ominfra/scripts/journald2aws.py +127 -28
 - ominfra/scripts/manage.py +1360 -485
 - ominfra/scripts/supervisor.py +59 -10
 - {ominfra-0.0.0.dev147.dist-info → ominfra-0.0.0.dev148.dist-info}/METADATA +3 -3
 - {ominfra-0.0.0.dev147.dist-info → ominfra-0.0.0.dev148.dist-info}/RECORD +25 -23
 - {ominfra-0.0.0.dev147.dist-info → ominfra-0.0.0.dev148.dist-info}/LICENSE +0 -0
 - {ominfra-0.0.0.dev147.dist-info → ominfra-0.0.0.dev148.dist-info}/WHEEL +0 -0
 - {ominfra-0.0.0.dev147.dist-info → ominfra-0.0.0.dev148.dist-info}/entry_points.txt +0 -0
 - {ominfra-0.0.0.dev147.dist-info → ominfra-0.0.0.dev148.dist-info}/top_level.txt +0 -0
 
    
        ominfra/configs.py
    CHANGED
    
    | 
         @@ -1,6 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            # ruff: noqa: UP006 UP007
         
     | 
| 
       2 
2 
     | 
    
         
             
            # @omlish-lite
         
     | 
| 
       3 
3 
     | 
    
         
             
            import json
         
     | 
| 
      
 4 
     | 
    
         
            +
            import os.path
         
     | 
| 
       4 
5 
     | 
    
         
             
            import typing as ta
         
     | 
| 
       5 
6 
     | 
    
         | 
| 
       6 
7 
     | 
    
         
             
            from omdev.toml.parser import toml_loads
         
     | 
| 
         @@ -14,6 +15,33 @@ T = ta.TypeVar('T') 
     | 
|
| 
       14 
15 
     | 
    
         
             
            ConfigMapping = ta.Mapping[str, ta.Any]
         
     | 
| 
       15 
16 
     | 
    
         | 
| 
       16 
17 
     | 
    
         | 
| 
      
 18 
     | 
    
         
            +
            def parse_config_file(
         
     | 
| 
      
 19 
     | 
    
         
            +
                    name: str,
         
     | 
| 
      
 20 
     | 
    
         
            +
                    f: ta.TextIO,
         
     | 
| 
      
 21 
     | 
    
         
            +
            ) -> ConfigMapping:
         
     | 
| 
      
 22 
     | 
    
         
            +
                if name.endswith('.toml'):
         
     | 
| 
      
 23 
     | 
    
         
            +
                    return toml_loads(f.read())
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                elif any(name.endswith(e) for e in ('.yml', '.yaml')):
         
     | 
| 
      
 26 
     | 
    
         
            +
                    yaml = __import__('yaml')
         
     | 
| 
      
 27 
     | 
    
         
            +
                    return yaml.safe_load(f)
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                elif name.endswith('.ini'):
         
     | 
| 
      
 30 
     | 
    
         
            +
                    import configparser
         
     | 
| 
      
 31 
     | 
    
         
            +
                    cp = configparser.ConfigParser()
         
     | 
| 
      
 32 
     | 
    
         
            +
                    cp.read_file(f)
         
     | 
| 
      
 33 
     | 
    
         
            +
                    config_dct: ta.Dict[str, ta.Any] = {}
         
     | 
| 
      
 34 
     | 
    
         
            +
                    for sec in cp.sections():
         
     | 
| 
      
 35 
     | 
    
         
            +
                        cd = config_dct
         
     | 
| 
      
 36 
     | 
    
         
            +
                        for k in sec.split('.'):
         
     | 
| 
      
 37 
     | 
    
         
            +
                            cd = cd.setdefault(k, {})
         
     | 
| 
      
 38 
     | 
    
         
            +
                        cd.update(cp.items(sec))
         
     | 
| 
      
 39 
     | 
    
         
            +
                    return config_dct
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                else:
         
     | 
| 
      
 42 
     | 
    
         
            +
                    return json.loads(f.read())
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
       17 
45 
     | 
    
         
             
            def read_config_file(
         
     | 
| 
       18 
46 
     | 
    
         
             
                    path: str,
         
     | 
| 
       19 
47 
     | 
    
         
             
                    cls: ta.Type[T],
         
     | 
| 
         @@ -21,13 +49,10 @@ def read_config_file( 
     | 
|
| 
       21 
49 
     | 
    
         
             
                    prepare: ta.Optional[ta.Callable[[ConfigMapping], ConfigMapping]] = None,
         
     | 
| 
       22 
50 
     | 
    
         
             
            ) -> T:
         
     | 
| 
       23 
51 
     | 
    
         
             
                with open(path) as cf:
         
     | 
| 
       24 
     | 
    
         
            -
                     
     | 
| 
       25 
     | 
    
         
            -
                        config_dct = toml_loads(cf.read())
         
     | 
| 
       26 
     | 
    
         
            -
                    else:
         
     | 
| 
       27 
     | 
    
         
            -
                        config_dct = json.loads(cf.read())
         
     | 
| 
      
 52 
     | 
    
         
            +
                    config_dct = parse_config_file(os.path.basename(path), cf)
         
     | 
| 
       28 
53 
     | 
    
         | 
| 
       29 
54 
     | 
    
         
             
                if prepare is not None:
         
     | 
| 
       30 
     | 
    
         
            -
                    config_dct = prepare(config_dct) 
     | 
| 
      
 55 
     | 
    
         
            +
                    config_dct = prepare(config_dct)
         
     | 
| 
       31 
56 
     | 
    
         | 
| 
       32 
57 
     | 
    
         
             
                return unmarshal_obj(config_dct, cls)
         
     | 
| 
       33 
58 
     | 
    
         | 
    
        ominfra/manage/commands/base.py
    CHANGED
    
    | 
         @@ -23,8 +23,8 @@ class Command(abc.ABC, ta.Generic[CommandOutputT]): 
     | 
|
| 
       23 
23 
     | 
    
         
             
                    pass
         
     | 
| 
       24 
24 
     | 
    
         | 
| 
       25 
25 
     | 
    
         
             
                @ta.final
         
     | 
| 
       26 
     | 
    
         
            -
                def execute(self, executor: 'CommandExecutor') -> CommandOutputT:
         
     | 
| 
       27 
     | 
    
         
            -
                    return check_isinstance(executor.execute(self), self.Output)  # type: ignore[return-value]
         
     | 
| 
      
 26 
     | 
    
         
            +
                async def execute(self, executor: 'CommandExecutor') -> CommandOutputT:
         
     | 
| 
      
 27 
     | 
    
         
            +
                    return check_isinstance(await executor.execute(self), self.Output)  # type: ignore[return-value]
         
     | 
| 
       28 
28 
     | 
    
         | 
| 
       29 
29 
     | 
    
         | 
| 
       30 
30 
     | 
    
         
             
            ##
         
     | 
| 
         @@ -85,10 +85,10 @@ class CommandOutputOrExceptionData(CommandOutputOrException): 
     | 
|
| 
       85 
85 
     | 
    
         | 
| 
       86 
86 
     | 
    
         
             
            class CommandExecutor(abc.ABC, ta.Generic[CommandT, CommandOutputT]):
         
     | 
| 
       87 
87 
     | 
    
         
             
                @abc.abstractmethod
         
     | 
| 
       88 
     | 
    
         
            -
                def execute(self, cmd: CommandT) -> CommandOutputT:
         
     | 
| 
      
 88 
     | 
    
         
            +
                def execute(self, cmd: CommandT) -> ta.Awaitable[CommandOutputT]:
         
     | 
| 
       89 
89 
     | 
    
         
             
                    raise NotImplementedError
         
     | 
| 
       90 
90 
     | 
    
         | 
| 
       91 
     | 
    
         
            -
                def try_execute(
         
     | 
| 
      
 91 
     | 
    
         
            +
                async def try_execute(
         
     | 
| 
       92 
92 
     | 
    
         
             
                        self,
         
     | 
| 
       93 
93 
     | 
    
         
             
                        cmd: CommandT,
         
     | 
| 
       94 
94 
     | 
    
         
             
                        *,
         
     | 
| 
         @@ -96,7 +96,7 @@ class CommandExecutor(abc.ABC, ta.Generic[CommandT, CommandOutputT]): 
     | 
|
| 
       96 
96 
     | 
    
         
             
                        omit_exc_object: bool = False,
         
     | 
| 
       97 
97 
     | 
    
         
             
                ) -> CommandOutputOrException[CommandOutputT]:
         
     | 
| 
       98 
98 
     | 
    
         
             
                    try:
         
     | 
| 
       99 
     | 
    
         
            -
                        o = self.execute(cmd)
         
     | 
| 
      
 99 
     | 
    
         
            +
                        o = await self.execute(cmd)
         
     | 
| 
       100 
100 
     | 
    
         | 
| 
       101 
101 
     | 
    
         
             
                    except Exception as e:  # noqa
         
     | 
| 
       102 
102 
     | 
    
         
             
                        if log is not None:
         
     | 
| 
         @@ -18,6 +18,6 @@ class LocalCommandExecutor(CommandExecutor): 
     | 
|
| 
       18 
18 
     | 
    
         | 
| 
       19 
19 
     | 
    
         
             
                    self._command_executors = command_executors
         
     | 
| 
       20 
20 
     | 
    
         | 
| 
       21 
     | 
    
         
            -
                def execute(self, cmd: Command) -> Command.Output:
         
     | 
| 
      
 21 
     | 
    
         
            +
                async def execute(self, cmd: Command) -> Command.Output:
         
     | 
| 
       22 
22 
     | 
    
         
             
                    ce: CommandExecutor = self._command_executors[type(cmd)]
         
     | 
| 
       23 
     | 
    
         
            -
                    return ce.execute(cmd)
         
     | 
| 
      
 23 
     | 
    
         
            +
                    return await ce.execute(cmd)
         
     | 
| 
         @@ -54,7 +54,7 @@ def bind_command( 
     | 
|
| 
       54 
54 
     | 
    
         
             
            class _FactoryCommandExecutor(CommandExecutor):
         
     | 
| 
       55 
55 
     | 
    
         
             
                factory: ta.Callable[[], CommandExecutor]
         
     | 
| 
       56 
56 
     | 
    
         | 
| 
       57 
     | 
    
         
            -
                def execute(self, i: Command) -> Command.Output:
         
     | 
| 
      
 57 
     | 
    
         
            +
                def execute(self, i: Command) -> ta.Awaitable[Command.Output]:
         
     | 
| 
       58 
58 
     | 
    
         
             
                    return self.factory().execute(i)
         
     | 
| 
       59 
59 
     | 
    
         | 
| 
       60 
60 
     | 
    
         | 
| 
         @@ -29,9 +29,9 @@ class InterpCommand(Command['InterpCommand.Output']): 
     | 
|
| 
       29 
29 
     | 
    
         | 
| 
       30 
30 
     | 
    
         | 
| 
       31 
31 
     | 
    
         
             
            class InterpCommandExecutor(CommandExecutor[InterpCommand, InterpCommand.Output]):
         
     | 
| 
       32 
     | 
    
         
            -
                def execute(self, cmd: InterpCommand) -> InterpCommand.Output:
         
     | 
| 
      
 32 
     | 
    
         
            +
                async def execute(self, cmd: InterpCommand) -> InterpCommand.Output:
         
     | 
| 
       33 
33 
     | 
    
         
             
                    i = InterpSpecifier.parse(check_not_none(cmd.spec))
         
     | 
| 
       34 
     | 
    
         
            -
                    o = check_not_none(DEFAULT_INTERP_RESOLVER.resolve(i, install=cmd.install))
         
     | 
| 
      
 34 
     | 
    
         
            +
                    o = check_not_none(await DEFAULT_INTERP_RESOLVER.resolve(i, install=cmd.install))
         
     | 
| 
       35 
35 
     | 
    
         
             
                    return InterpCommand.Output(
         
     | 
| 
       36 
36 
     | 
    
         
             
                        exe=o.exe,
         
     | 
| 
       37 
37 
     | 
    
         
             
                        version=str(o.version.version),
         
     | 
| 
         @@ -1,11 +1,15 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            # ruff: noqa: UP006 UP007
         
     | 
| 
      
 1 
     | 
    
         
            +
            # ruff: noqa: TC003 UP006 UP007
         
     | 
| 
      
 2 
     | 
    
         
            +
            import asyncio.subprocess
         
     | 
| 
       2 
3 
     | 
    
         
             
            import dataclasses as dc
         
     | 
| 
       3 
4 
     | 
    
         
             
            import os
         
     | 
| 
       4 
5 
     | 
    
         
             
            import subprocess
         
     | 
| 
       5 
6 
     | 
    
         
             
            import time
         
     | 
| 
       6 
7 
     | 
    
         
             
            import typing as ta
         
     | 
| 
       7 
8 
     | 
    
         | 
| 
      
 9 
     | 
    
         
            +
            from omlish.lite.asyncio.subprocesses import asyncio_subprocess_communicate
         
     | 
| 
      
 10 
     | 
    
         
            +
            from omlish.lite.asyncio.subprocesses import asyncio_subprocess_popen
         
     | 
| 
       8 
11 
     | 
    
         
             
            from omlish.lite.check import check_not_isinstance
         
     | 
| 
      
 12 
     | 
    
         
            +
            from omlish.lite.check import check_not_none
         
     | 
| 
       9 
13 
     | 
    
         
             
            from omlish.lite.subprocesses import SUBPROCESS_CHANNEL_OPTION_VALUES
         
     | 
| 
       10 
14 
     | 
    
         
             
            from omlish.lite.subprocesses import SubprocessChannelOption
         
     | 
| 
       11 
15 
     | 
    
         
             
            from omlish.lite.subprocesses import subprocess_maybe_shell_wrap_exec
         
     | 
| 
         @@ -49,27 +53,31 @@ class SubprocessCommand(Command['SubprocessCommand.Output']): 
     | 
|
| 
       49 
53 
     | 
    
         | 
| 
       50 
54 
     | 
    
         | 
| 
       51 
55 
     | 
    
         
             
            class SubprocessCommandExecutor(CommandExecutor[SubprocessCommand, SubprocessCommand.Output]):
         
     | 
| 
       52 
     | 
    
         
            -
                def execute(self,  
     | 
| 
       53 
     | 
    
         
            -
                     
     | 
| 
       54 
     | 
    
         
            -
             
     | 
| 
      
 56 
     | 
    
         
            +
                async def execute(self, cmd: SubprocessCommand) -> SubprocessCommand.Output:
         
     | 
| 
      
 57 
     | 
    
         
            +
                    proc: asyncio.subprocess.Process
         
     | 
| 
      
 58 
     | 
    
         
            +
                    async with asyncio_subprocess_popen(
         
     | 
| 
      
 59 
     | 
    
         
            +
                        *subprocess_maybe_shell_wrap_exec(*cmd.cmd),
         
     | 
| 
       55 
60 
     | 
    
         | 
| 
       56 
     | 
    
         
            -
                        shell= 
     | 
| 
       57 
     | 
    
         
            -
                        cwd= 
     | 
| 
       58 
     | 
    
         
            -
                        env={**os.environ, **( 
     | 
| 
      
 61 
     | 
    
         
            +
                        shell=cmd.shell,
         
     | 
| 
      
 62 
     | 
    
         
            +
                        cwd=cmd.cwd,
         
     | 
| 
      
 63 
     | 
    
         
            +
                        env={**os.environ, **(cmd.env or {})},
         
     | 
| 
       59 
64 
     | 
    
         | 
| 
       60 
     | 
    
         
            -
                        stdin=subprocess.PIPE if  
     | 
| 
       61 
     | 
    
         
            -
                        stdout=SUBPROCESS_CHANNEL_OPTION_VALUES[ta.cast(SubprocessChannelOption,  
     | 
| 
       62 
     | 
    
         
            -
                        stderr=SUBPROCESS_CHANNEL_OPTION_VALUES[ta.cast(SubprocessChannelOption,  
     | 
| 
      
 65 
     | 
    
         
            +
                        stdin=subprocess.PIPE if cmd.input is not None else None,
         
     | 
| 
      
 66 
     | 
    
         
            +
                        stdout=SUBPROCESS_CHANNEL_OPTION_VALUES[ta.cast(SubprocessChannelOption, cmd.stdout)],
         
     | 
| 
      
 67 
     | 
    
         
            +
                        stderr=SUBPROCESS_CHANNEL_OPTION_VALUES[ta.cast(SubprocessChannelOption, cmd.stderr)],
         
     | 
| 
      
 68 
     | 
    
         
            +
             
     | 
| 
      
 69 
     | 
    
         
            +
                        timeout=cmd.timeout,
         
     | 
| 
       63 
70 
     | 
    
         
             
                    ) as proc:
         
     | 
| 
       64 
71 
     | 
    
         
             
                        start_time = time.time()
         
     | 
| 
       65 
     | 
    
         
            -
                        stdout, stderr =  
     | 
| 
       66 
     | 
    
         
            -
                             
     | 
| 
       67 
     | 
    
         
            -
                             
     | 
| 
      
 72 
     | 
    
         
            +
                        stdout, stderr = await asyncio_subprocess_communicate(
         
     | 
| 
      
 73 
     | 
    
         
            +
                            proc,
         
     | 
| 
      
 74 
     | 
    
         
            +
                            input=cmd.input,
         
     | 
| 
      
 75 
     | 
    
         
            +
                            timeout=cmd.timeout,
         
     | 
| 
       68 
76 
     | 
    
         
             
                        )
         
     | 
| 
       69 
77 
     | 
    
         
             
                        end_time = time.time()
         
     | 
| 
       70 
78 
     | 
    
         | 
| 
       71 
79 
     | 
    
         
             
                    return SubprocessCommand.Output(
         
     | 
| 
       72 
     | 
    
         
            -
                        rc=proc.returncode,
         
     | 
| 
      
 80 
     | 
    
         
            +
                        rc=check_not_none(proc.returncode),
         
     | 
| 
       73 
81 
     | 
    
         
             
                        pid=proc.pid,
         
     | 
| 
       74 
82 
     | 
    
         | 
| 
       75 
83 
     | 
    
         
             
                        elapsed_s=end_time - start_time,
         
     | 
    
        ominfra/manage/deploy/command.py
    CHANGED
    
    | 
         @@ -21,7 +21,7 @@ class DeployCommand(Command['DeployCommand.Output']): 
     | 
|
| 
       21 
21 
     | 
    
         | 
| 
       22 
22 
     | 
    
         | 
| 
       23 
23 
     | 
    
         
             
            class DeployCommandExecutor(CommandExecutor[DeployCommand, DeployCommand.Output]):
         
     | 
| 
       24 
     | 
    
         
            -
                def execute(self, cmd: DeployCommand) -> DeployCommand.Output:
         
     | 
| 
      
 24 
     | 
    
         
            +
                async def execute(self, cmd: DeployCommand) -> DeployCommand.Output:
         
     | 
| 
       25 
25 
     | 
    
         
             
                    log.info('Deploying!')
         
     | 
| 
       26 
26 
     | 
    
         | 
| 
       27 
27 
     | 
    
         
             
                    return DeployCommand.Output()
         
     | 
    
        ominfra/manage/main.py
    CHANGED
    
    | 
         @@ -5,6 +5,7 @@ 
     | 
|
| 
       5 
5 
     | 
    
         
             
            manage.py -s 'docker run -i python:3.12'
         
     | 
| 
       6 
6 
     | 
    
         
             
            manage.py -s 'ssh -i /foo/bar.pem foo@bar.baz' -q --python=python3.8
         
     | 
| 
       7 
7 
     | 
    
         
             
            """
         
     | 
| 
      
 8 
     | 
    
         
            +
            import asyncio
         
     | 
| 
       8 
9 
     | 
    
         
             
            import contextlib
         
     | 
| 
       9 
10 
     | 
    
         
             
            import json
         
     | 
| 
       10 
11 
     | 
    
         
             
            import typing as ta
         
     | 
| 
         @@ -21,38 +22,14 @@ from .commands.base import CommandExecutor 
     | 
|
| 
       21 
22 
     | 
    
         
             
            from .commands.execution import LocalCommandExecutor
         
     | 
| 
       22 
23 
     | 
    
         
             
            from .config import MainConfig
         
     | 
| 
       23 
24 
     | 
    
         
             
            from .remote.config import RemoteConfig
         
     | 
| 
       24 
     | 
    
         
            -
            from .remote. 
     | 
| 
      
 25 
     | 
    
         
            +
            from .remote.connection import RemoteExecutionConnector
         
     | 
| 
       25 
26 
     | 
    
         
             
            from .remote.spawning import RemoteSpawning
         
     | 
| 
       26 
27 
     | 
    
         | 
| 
       27 
28 
     | 
    
         | 
| 
       28 
29 
     | 
    
         
             
            ##
         
     | 
| 
       29 
30 
     | 
    
         | 
| 
       30 
31 
     | 
    
         | 
| 
       31 
     | 
    
         
            -
            def  
     | 
| 
       32 
     | 
    
         
            -
                import argparse
         
     | 
| 
       33 
     | 
    
         
            -
             
     | 
| 
       34 
     | 
    
         
            -
                parser = argparse.ArgumentParser()
         
     | 
| 
       35 
     | 
    
         
            -
             
     | 
| 
       36 
     | 
    
         
            -
                parser.add_argument('--_payload-file')
         
     | 
| 
       37 
     | 
    
         
            -
             
     | 
| 
       38 
     | 
    
         
            -
                parser.add_argument('-s', '--shell')
         
     | 
| 
       39 
     | 
    
         
            -
                parser.add_argument('-q', '--shell-quote', action='store_true')
         
     | 
| 
       40 
     | 
    
         
            -
                parser.add_argument('--python', default='python3')
         
     | 
| 
       41 
     | 
    
         
            -
             
     | 
| 
       42 
     | 
    
         
            -
                parser.add_argument('--pycharm-debug-port', type=int)
         
     | 
| 
       43 
     | 
    
         
            -
                parser.add_argument('--pycharm-debug-host')
         
     | 
| 
       44 
     | 
    
         
            -
                parser.add_argument('--pycharm-debug-version')
         
     | 
| 
       45 
     | 
    
         
            -
             
     | 
| 
       46 
     | 
    
         
            -
                parser.add_argument('--debug', action='store_true')
         
     | 
| 
       47 
     | 
    
         
            -
             
     | 
| 
       48 
     | 
    
         
            -
                parser.add_argument('--local', action='store_true')
         
     | 
| 
       49 
     | 
    
         
            -
             
     | 
| 
       50 
     | 
    
         
            -
                parser.add_argument('command', nargs='+')
         
     | 
| 
       51 
     | 
    
         
            -
             
     | 
| 
       52 
     | 
    
         
            -
                args = parser.parse_args()
         
     | 
| 
       53 
     | 
    
         
            -
             
     | 
| 
       54 
     | 
    
         
            -
                #
         
     | 
| 
       55 
     | 
    
         
            -
             
     | 
| 
      
 32 
     | 
    
         
            +
            async def _async_main(args: ta.Any) -> None:
         
     | 
| 
       56 
33 
     | 
    
         
             
                bs = MainBootstrap(
         
     | 
| 
       57 
34 
     | 
    
         
             
                    main_config=MainConfig(
         
     | 
| 
       58 
35 
     | 
    
         
             
                        log_level='DEBUG' if args.debug else 'INFO',
         
     | 
| 
         @@ -65,12 +42,16 @@ def _main() -> None: 
     | 
|
| 
       65 
42 
     | 
    
         | 
| 
       66 
43 
     | 
    
         
             
                        pycharm_remote_debug=PycharmRemoteDebug(
         
     | 
| 
       67 
44 
     | 
    
         
             
                            port=args.pycharm_debug_port,
         
     | 
| 
       68 
     | 
    
         
            -
                            host=args.pycharm_debug_host,
         
     | 
| 
      
 45 
     | 
    
         
            +
                            **(dict(host=args.pycharm_debug_host) if args.pycharm_debug_host is not None else {}),
         
     | 
| 
       69 
46 
     | 
    
         
             
                            install_version=args.pycharm_debug_version,
         
     | 
| 
       70 
47 
     | 
    
         
             
                        ) if args.pycharm_debug_port is not None else None,
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
                        timebomb_delay_s=args.remote_timebomb_delay_s,
         
     | 
| 
       71 
50 
     | 
    
         
             
                    ),
         
     | 
| 
       72 
51 
     | 
    
         
             
                )
         
     | 
| 
       73 
52 
     | 
    
         | 
| 
      
 53 
     | 
    
         
            +
                #
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
       74 
55 
     | 
    
         
             
                injector = main_bootstrap(
         
     | 
| 
       75 
56 
     | 
    
         
             
                    bs,
         
     | 
| 
       76 
57 
     | 
    
         
             
                )
         
     | 
| 
         @@ -89,7 +70,7 @@ def _main() -> None: 
     | 
|
| 
       89 
70 
     | 
    
         | 
| 
       90 
71 
     | 
    
         
             
                #
         
     | 
| 
       91 
72 
     | 
    
         | 
| 
       92 
     | 
    
         
            -
                with contextlib. 
     | 
| 
      
 73 
     | 
    
         
            +
                async with contextlib.AsyncExitStack() as es:
         
     | 
| 
       93 
74 
     | 
    
         
             
                    ce: CommandExecutor
         
     | 
| 
       94 
75 
     | 
    
         | 
| 
       95 
76 
     | 
    
         
             
                    if args.local:
         
     | 
| 
         @@ -102,16 +83,51 @@ def _main() -> None: 
     | 
|
| 
       102 
83 
     | 
    
         
             
                            python=args.python,
         
     | 
| 
       103 
84 
     | 
    
         
             
                        )
         
     | 
| 
       104 
85 
     | 
    
         | 
| 
       105 
     | 
    
         
            -
                        ce = es. 
     | 
| 
      
 86 
     | 
    
         
            +
                        ce = await es.enter_async_context(injector[RemoteExecutionConnector].connect(tgt, bs))  # noqa
         
     | 
| 
       106 
87 
     | 
    
         | 
| 
       107 
     | 
    
         
            -
                     
     | 
| 
       108 
     | 
    
         
            -
                         
     | 
| 
      
 88 
     | 
    
         
            +
                    async def run_command(cmd: Command) -> None:
         
     | 
| 
      
 89 
     | 
    
         
            +
                        res = await ce.try_execute(
         
     | 
| 
       109 
90 
     | 
    
         
             
                            cmd,
         
     | 
| 
       110 
91 
     | 
    
         
             
                            log=log,
         
     | 
| 
       111 
92 
     | 
    
         
             
                            omit_exc_object=True,
         
     | 
| 
       112 
93 
     | 
    
         
             
                        )
         
     | 
| 
       113 
94 
     | 
    
         | 
| 
       114 
     | 
    
         
            -
                        print(msh.marshal_obj( 
     | 
| 
      
 95 
     | 
    
         
            +
                        print(msh.marshal_obj(res, opts=ObjMarshalOptions(raw_bytes=True)))
         
     | 
| 
      
 96 
     | 
    
         
            +
             
     | 
| 
      
 97 
     | 
    
         
            +
                    await asyncio.gather(*[
         
     | 
| 
      
 98 
     | 
    
         
            +
                        run_command(cmd)
         
     | 
| 
      
 99 
     | 
    
         
            +
                        for cmd in cmds
         
     | 
| 
      
 100 
     | 
    
         
            +
                    ])
         
     | 
| 
      
 101 
     | 
    
         
            +
             
     | 
| 
      
 102 
     | 
    
         
            +
             
     | 
| 
      
 103 
     | 
    
         
            +
            def _main() -> None:
         
     | 
| 
      
 104 
     | 
    
         
            +
                import argparse
         
     | 
| 
      
 105 
     | 
    
         
            +
             
     | 
| 
      
 106 
     | 
    
         
            +
                parser = argparse.ArgumentParser()
         
     | 
| 
      
 107 
     | 
    
         
            +
             
     | 
| 
      
 108 
     | 
    
         
            +
                parser.add_argument('--_payload-file')
         
     | 
| 
      
 109 
     | 
    
         
            +
             
     | 
| 
      
 110 
     | 
    
         
            +
                parser.add_argument('-s', '--shell')
         
     | 
| 
      
 111 
     | 
    
         
            +
                parser.add_argument('-q', '--shell-quote', action='store_true')
         
     | 
| 
      
 112 
     | 
    
         
            +
                parser.add_argument('--python', default='python3')
         
     | 
| 
      
 113 
     | 
    
         
            +
             
     | 
| 
      
 114 
     | 
    
         
            +
                parser.add_argument('--pycharm-debug-port', type=int)
         
     | 
| 
      
 115 
     | 
    
         
            +
                parser.add_argument('--pycharm-debug-host')
         
     | 
| 
      
 116 
     | 
    
         
            +
                parser.add_argument('--pycharm-debug-version')
         
     | 
| 
      
 117 
     | 
    
         
            +
             
     | 
| 
      
 118 
     | 
    
         
            +
                parser.add_argument('--remote-timebomb-delay-s', type=float)
         
     | 
| 
      
 119 
     | 
    
         
            +
             
     | 
| 
      
 120 
     | 
    
         
            +
                parser.add_argument('--debug', action='store_true')
         
     | 
| 
      
 121 
     | 
    
         
            +
             
     | 
| 
      
 122 
     | 
    
         
            +
                parser.add_argument('--local', action='store_true')
         
     | 
| 
      
 123 
     | 
    
         
            +
             
     | 
| 
      
 124 
     | 
    
         
            +
                parser.add_argument('command', nargs='+')
         
     | 
| 
      
 125 
     | 
    
         
            +
             
     | 
| 
      
 126 
     | 
    
         
            +
                args = parser.parse_args()
         
     | 
| 
      
 127 
     | 
    
         
            +
             
     | 
| 
      
 128 
     | 
    
         
            +
                #
         
     | 
| 
      
 129 
     | 
    
         
            +
             
     | 
| 
      
 130 
     | 
    
         
            +
                asyncio.run(_async_main(args))
         
     | 
| 
       115 
131 
     | 
    
         | 
| 
       116 
132 
     | 
    
         | 
| 
       117 
133 
     | 
    
         
             
            if __name__ == '__main__':
         
     | 
| 
         @@ -0,0 +1,172 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # ruff: noqa: UP006 UP007
         
     | 
| 
      
 2 
     | 
    
         
            +
            import asyncio
         
     | 
| 
      
 3 
     | 
    
         
            +
            import functools
         
     | 
| 
      
 4 
     | 
    
         
            +
            import logging
         
     | 
| 
      
 5 
     | 
    
         
            +
            import os
         
     | 
| 
      
 6 
     | 
    
         
            +
            import signal
         
     | 
| 
      
 7 
     | 
    
         
            +
            import threading
         
     | 
| 
      
 8 
     | 
    
         
            +
            import time
         
     | 
| 
      
 9 
     | 
    
         
            +
            import typing as ta
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
            from omlish.lite.asyncio.asyncio import asyncio_open_stream_reader
         
     | 
| 
      
 12 
     | 
    
         
            +
            from omlish.lite.asyncio.asyncio import asyncio_open_stream_writer
         
     | 
| 
      
 13 
     | 
    
         
            +
            from omlish.lite.cached import cached_nullary
         
     | 
| 
      
 14 
     | 
    
         
            +
            from omlish.lite.check import check_none
         
     | 
| 
      
 15 
     | 
    
         
            +
            from omlish.lite.check import check_not_none
         
     | 
| 
      
 16 
     | 
    
         
            +
            from omlish.lite.deathsig import set_process_deathsig
         
     | 
| 
      
 17 
     | 
    
         
            +
            from omlish.lite.inject import Injector
         
     | 
| 
      
 18 
     | 
    
         
            +
            from omlish.lite.logs import log
         
     | 
| 
      
 19 
     | 
    
         
            +
            from omlish.lite.marshal import ObjMarshalerManager
         
     | 
| 
      
 20 
     | 
    
         
            +
            from omlish.lite.pycharm import pycharm_debug_connect
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
            from ...pyremote import pyremote_bootstrap_finalize
         
     | 
| 
      
 23 
     | 
    
         
            +
            from ..bootstrap import MainBootstrap
         
     | 
| 
      
 24 
     | 
    
         
            +
            from ..commands.execution import LocalCommandExecutor
         
     | 
| 
      
 25 
     | 
    
         
            +
            from .channel import RemoteChannel
         
     | 
| 
      
 26 
     | 
    
         
            +
            from .channel import RemoteChannelImpl
         
     | 
| 
      
 27 
     | 
    
         
            +
            from .execution import _RemoteCommandHandler
         
     | 
| 
      
 28 
     | 
    
         
            +
            from .execution import _RemoteLogHandler
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
            if ta.TYPE_CHECKING:
         
     | 
| 
      
 32 
     | 
    
         
            +
                from ..bootstrap_ import main_bootstrap
         
     | 
| 
      
 33 
     | 
    
         
            +
            else:
         
     | 
| 
      
 34 
     | 
    
         
            +
                main_bootstrap: ta.Any = None
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
            ##
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
            class _RemoteExecutionLogHandler(logging.Handler):
         
     | 
| 
      
 41 
     | 
    
         
            +
                def __init__(self, fn: ta.Callable[[str], None]) -> None:
         
     | 
| 
      
 42 
     | 
    
         
            +
                    super().__init__()
         
     | 
| 
      
 43 
     | 
    
         
            +
                    self._fn = fn
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                def emit(self, record):
         
     | 
| 
      
 46 
     | 
    
         
            +
                    msg = self.format(record)
         
     | 
| 
      
 47 
     | 
    
         
            +
                    self._fn(msg)
         
     | 
| 
      
 48 
     | 
    
         
            +
             
     | 
| 
      
 49 
     | 
    
         
            +
             
     | 
| 
      
 50 
     | 
    
         
            +
            ##
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
             
     | 
| 
      
 53 
     | 
    
         
            +
            class _RemoteExecutionMain:
         
     | 
| 
      
 54 
     | 
    
         
            +
                def __init__(
         
     | 
| 
      
 55 
     | 
    
         
            +
                        self,
         
     | 
| 
      
 56 
     | 
    
         
            +
                        chan: RemoteChannel,
         
     | 
| 
      
 57 
     | 
    
         
            +
                ) -> None:
         
     | 
| 
      
 58 
     | 
    
         
            +
                    super().__init__()
         
     | 
| 
      
 59 
     | 
    
         
            +
             
     | 
| 
      
 60 
     | 
    
         
            +
                    self._chan = chan
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
                    self.__bootstrap: ta.Optional[MainBootstrap] = None
         
     | 
| 
      
 63 
     | 
    
         
            +
                    self.__injector: ta.Optional[Injector] = None
         
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
      
 65 
     | 
    
         
            +
                @property
         
     | 
| 
      
 66 
     | 
    
         
            +
                def _bootstrap(self) -> MainBootstrap:
         
     | 
| 
      
 67 
     | 
    
         
            +
                    return check_not_none(self.__bootstrap)
         
     | 
| 
      
 68 
     | 
    
         
            +
             
     | 
| 
      
 69 
     | 
    
         
            +
                @property
         
     | 
| 
      
 70 
     | 
    
         
            +
                def _injector(self) -> Injector:
         
     | 
| 
      
 71 
     | 
    
         
            +
                    return check_not_none(self.__injector)
         
     | 
| 
      
 72 
     | 
    
         
            +
             
     | 
| 
      
 73 
     | 
    
         
            +
                #
         
     | 
| 
      
 74 
     | 
    
         
            +
             
     | 
| 
      
 75 
     | 
    
         
            +
                def _timebomb_main(
         
     | 
| 
      
 76 
     | 
    
         
            +
                        self,
         
     | 
| 
      
 77 
     | 
    
         
            +
                        delay_s: float,
         
     | 
| 
      
 78 
     | 
    
         
            +
                        *,
         
     | 
| 
      
 79 
     | 
    
         
            +
                        sig: int = signal.SIGINT,
         
     | 
| 
      
 80 
     | 
    
         
            +
                        code: int = 1,
         
     | 
| 
      
 81 
     | 
    
         
            +
                ) -> None:
         
     | 
| 
      
 82 
     | 
    
         
            +
                    time.sleep(delay_s)
         
     | 
| 
      
 83 
     | 
    
         
            +
             
     | 
| 
      
 84 
     | 
    
         
            +
                    if (pgid := os.getpgid(0)) == os.getpid():
         
     | 
| 
      
 85 
     | 
    
         
            +
                        os.killpg(pgid, sig)
         
     | 
| 
      
 86 
     | 
    
         
            +
             
     | 
| 
      
 87 
     | 
    
         
            +
                    os._exit(code)  # noqa
         
     | 
| 
      
 88 
     | 
    
         
            +
             
     | 
| 
      
 89 
     | 
    
         
            +
                @cached_nullary
         
     | 
| 
      
 90 
     | 
    
         
            +
                def _timebomb_thread(self) -> ta.Optional[threading.Thread]:
         
     | 
| 
      
 91 
     | 
    
         
            +
                    if (tbd := self._bootstrap.remote_config.timebomb_delay_s) is None:
         
     | 
| 
      
 92 
     | 
    
         
            +
                        return None
         
     | 
| 
      
 93 
     | 
    
         
            +
             
     | 
| 
      
 94 
     | 
    
         
            +
                    thr = threading.Thread(
         
     | 
| 
      
 95 
     | 
    
         
            +
                        target=functools.partial(self._timebomb_main, tbd),
         
     | 
| 
      
 96 
     | 
    
         
            +
                        name=f'{self.__class__.__name__}.timebomb',
         
     | 
| 
      
 97 
     | 
    
         
            +
                        daemon=True,
         
     | 
| 
      
 98 
     | 
    
         
            +
                    )
         
     | 
| 
      
 99 
     | 
    
         
            +
             
     | 
| 
      
 100 
     | 
    
         
            +
                    thr.start()
         
     | 
| 
      
 101 
     | 
    
         
            +
             
     | 
| 
      
 102 
     | 
    
         
            +
                    log.debug('Started timebomb thread: %r', thr)
         
     | 
| 
      
 103 
     | 
    
         
            +
             
     | 
| 
      
 104 
     | 
    
         
            +
                    return thr
         
     | 
| 
      
 105 
     | 
    
         
            +
             
     | 
| 
      
 106 
     | 
    
         
            +
                #
         
     | 
| 
      
 107 
     | 
    
         
            +
             
     | 
| 
      
 108 
     | 
    
         
            +
                @cached_nullary
         
     | 
| 
      
 109 
     | 
    
         
            +
                def _log_handler(self) -> _RemoteLogHandler:
         
     | 
| 
      
 110 
     | 
    
         
            +
                    return _RemoteLogHandler(self._chan)
         
     | 
| 
      
 111 
     | 
    
         
            +
             
     | 
| 
      
 112 
     | 
    
         
            +
                #
         
     | 
| 
      
 113 
     | 
    
         
            +
             
     | 
| 
      
 114 
     | 
    
         
            +
                async def _setup(self) -> None:
         
     | 
| 
      
 115 
     | 
    
         
            +
                    check_none(self.__bootstrap)
         
     | 
| 
      
 116 
     | 
    
         
            +
                    check_none(self.__injector)
         
     | 
| 
      
 117 
     | 
    
         
            +
             
     | 
| 
      
 118 
     | 
    
         
            +
                    # Bootstrap
         
     | 
| 
      
 119 
     | 
    
         
            +
             
     | 
| 
      
 120 
     | 
    
         
            +
                    self.__bootstrap = check_not_none(await self._chan.recv_obj(MainBootstrap))
         
     | 
| 
      
 121 
     | 
    
         
            +
             
     | 
| 
      
 122 
     | 
    
         
            +
                    if (prd := self._bootstrap.remote_config.pycharm_remote_debug) is not None:
         
     | 
| 
      
 123 
     | 
    
         
            +
                        pycharm_debug_connect(prd)
         
     | 
| 
      
 124 
     | 
    
         
            +
             
     | 
| 
      
 125 
     | 
    
         
            +
                    self.__injector = main_bootstrap(self._bootstrap)
         
     | 
| 
      
 126 
     | 
    
         
            +
             
     | 
| 
      
 127 
     | 
    
         
            +
                    self._chan.set_marshaler(self._injector[ObjMarshalerManager])
         
     | 
| 
      
 128 
     | 
    
         
            +
             
     | 
| 
      
 129 
     | 
    
         
            +
                    # Post-bootstrap
         
     | 
| 
      
 130 
     | 
    
         
            +
             
     | 
| 
      
 131 
     | 
    
         
            +
                    if self._bootstrap.remote_config.set_pgid:
         
     | 
| 
      
 132 
     | 
    
         
            +
                        if os.getpgid(0) != os.getpid():
         
     | 
| 
      
 133 
     | 
    
         
            +
                            log.debug('Setting pgid')
         
     | 
| 
      
 134 
     | 
    
         
            +
                            os.setpgid(0, 0)
         
     | 
| 
      
 135 
     | 
    
         
            +
             
     | 
| 
      
 136 
     | 
    
         
            +
                    if (ds := self._bootstrap.remote_config.deathsig) is not None:
         
     | 
| 
      
 137 
     | 
    
         
            +
                        log.debug('Setting deathsig: %s', ds)
         
     | 
| 
      
 138 
     | 
    
         
            +
                        set_process_deathsig(int(signal.Signals[f'SIG{ds.upper()}']))
         
     | 
| 
      
 139 
     | 
    
         
            +
             
     | 
| 
      
 140 
     | 
    
         
            +
                    self._timebomb_thread()
         
     | 
| 
      
 141 
     | 
    
         
            +
             
     | 
| 
      
 142 
     | 
    
         
            +
                    if self._bootstrap.remote_config.forward_logging:
         
     | 
| 
      
 143 
     | 
    
         
            +
                        log.debug('Installing log forwarder')
         
     | 
| 
      
 144 
     | 
    
         
            +
                        logging.root.addHandler(self._log_handler())
         
     | 
| 
      
 145 
     | 
    
         
            +
             
     | 
| 
      
 146 
     | 
    
         
            +
                #
         
     | 
| 
      
 147 
     | 
    
         
            +
             
     | 
| 
      
 148 
     | 
    
         
            +
                async def run(self) -> None:
         
     | 
| 
      
 149 
     | 
    
         
            +
                    await self._setup()
         
     | 
| 
      
 150 
     | 
    
         
            +
             
     | 
| 
      
 151 
     | 
    
         
            +
                    executor = self._injector[LocalCommandExecutor]
         
     | 
| 
      
 152 
     | 
    
         
            +
             
     | 
| 
      
 153 
     | 
    
         
            +
                    handler = _RemoteCommandHandler(self._chan, executor)
         
     | 
| 
      
 154 
     | 
    
         
            +
             
     | 
| 
      
 155 
     | 
    
         
            +
                    await handler.run()
         
     | 
| 
      
 156 
     | 
    
         
            +
             
     | 
| 
      
 157 
     | 
    
         
            +
             
     | 
| 
      
 158 
     | 
    
         
            +
            def _remote_execution_main() -> None:
         
     | 
| 
      
 159 
     | 
    
         
            +
                rt = pyremote_bootstrap_finalize()  # noqa
         
     | 
| 
      
 160 
     | 
    
         
            +
             
     | 
| 
      
 161 
     | 
    
         
            +
                async def inner() -> None:
         
     | 
| 
      
 162 
     | 
    
         
            +
                    input = await asyncio_open_stream_reader(rt.input)  # noqa
         
     | 
| 
      
 163 
     | 
    
         
            +
                    output = await asyncio_open_stream_writer(rt.output)
         
     | 
| 
      
 164 
     | 
    
         
            +
             
     | 
| 
      
 165 
     | 
    
         
            +
                    chan = RemoteChannelImpl(
         
     | 
| 
      
 166 
     | 
    
         
            +
                        input,
         
     | 
| 
      
 167 
     | 
    
         
            +
                        output,
         
     | 
| 
      
 168 
     | 
    
         
            +
                    )
         
     | 
| 
      
 169 
     | 
    
         
            +
             
     | 
| 
      
 170 
     | 
    
         
            +
                    await _RemoteExecutionMain(chan).run()
         
     | 
| 
      
 171 
     | 
    
         
            +
             
     | 
| 
      
 172 
     | 
    
         
            +
                asyncio.run(inner())
         
     | 
    
        ominfra/manage/remote/channel.py
    CHANGED
    
    | 
         @@ -1,7 +1,8 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            # ruff: noqa: UP006 UP007
         
     | 
| 
      
 2 
     | 
    
         
            +
            import abc
         
     | 
| 
      
 3 
     | 
    
         
            +
            import asyncio
         
     | 
| 
       2 
4 
     | 
    
         
             
            import json
         
     | 
| 
       3 
5 
     | 
    
         
             
            import struct
         
     | 
| 
       4 
     | 
    
         
            -
            import threading
         
     | 
| 
       5 
6 
     | 
    
         
             
            import typing as ta
         
     | 
| 
       6 
7 
     | 
    
         | 
| 
       7 
8 
     | 
    
         
             
            from omlish.lite.json import json_dumps_compact
         
     | 
| 
         @@ -12,11 +13,30 @@ from omlish.lite.marshal import ObjMarshalerManager 
     | 
|
| 
       12 
13 
     | 
    
         
             
            T = ta.TypeVar('T')
         
     | 
| 
       13 
14 
     | 
    
         | 
| 
       14 
15 
     | 
    
         | 
| 
       15 
     | 
    
         
            -
             
     | 
| 
      
 16 
     | 
    
         
            +
            ##
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
            class RemoteChannel(abc.ABC):
         
     | 
| 
      
 20 
     | 
    
         
            +
                @abc.abstractmethod
         
     | 
| 
      
 21 
     | 
    
         
            +
                def send_obj(self, o: ta.Any, ty: ta.Any = None) -> ta.Awaitable[None]:
         
     | 
| 
      
 22 
     | 
    
         
            +
                    raise NotImplementedError
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                @abc.abstractmethod
         
     | 
| 
      
 25 
     | 
    
         
            +
                def recv_obj(self, ty: ta.Type[T]) -> ta.Awaitable[ta.Optional[T]]:
         
     | 
| 
      
 26 
     | 
    
         
            +
                    raise NotImplementedError
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
                def set_marshaler(self, msh: ObjMarshalerManager) -> None:  # noqa
         
     | 
| 
      
 29 
     | 
    
         
            +
                    pass
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
            ##
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
            class RemoteChannelImpl(RemoteChannel):
         
     | 
| 
       16 
36 
     | 
    
         
             
                def __init__(
         
     | 
| 
       17 
37 
     | 
    
         
             
                        self,
         
     | 
| 
       18 
     | 
    
         
            -
                        input:  
     | 
| 
       19 
     | 
    
         
            -
                        output:  
     | 
| 
      
 38 
     | 
    
         
            +
                        input: asyncio.StreamReader,  # noqa
         
     | 
| 
      
 39 
     | 
    
         
            +
                        output: asyncio.StreamWriter,
         
     | 
| 
       20 
40 
     | 
    
         
             
                        *,
         
     | 
| 
       21 
41 
     | 
    
         
             
                        msh: ObjMarshalerManager = OBJ_MARSHALER_MANAGER,
         
     | 
| 
       22 
42 
     | 
    
         
             
                ) -> None:
         
     | 
| 
         @@ -26,38 +46,43 @@ class RemoteChannel: 
     | 
|
| 
       26 
46 
     | 
    
         
             
                    self._output = output
         
     | 
| 
       27 
47 
     | 
    
         
             
                    self._msh = msh
         
     | 
| 
       28 
48 
     | 
    
         | 
| 
       29 
     | 
    
         
            -
                    self. 
     | 
| 
      
 49 
     | 
    
         
            +
                    self._input_lock = asyncio.Lock()
         
     | 
| 
      
 50 
     | 
    
         
            +
                    self._output_lock = asyncio.Lock()
         
     | 
| 
       30 
51 
     | 
    
         | 
| 
       31 
52 
     | 
    
         
             
                def set_marshaler(self, msh: ObjMarshalerManager) -> None:
         
     | 
| 
       32 
53 
     | 
    
         
             
                    self._msh = msh
         
     | 
| 
       33 
54 
     | 
    
         | 
| 
       34 
     | 
    
         
            -
                 
     | 
| 
      
 55 
     | 
    
         
            +
                #
         
     | 
| 
      
 56 
     | 
    
         
            +
             
     | 
| 
      
 57 
     | 
    
         
            +
                async def _send_obj(self, o: ta.Any, ty: ta.Any = None) -> None:
         
     | 
| 
       35 
58 
     | 
    
         
             
                    j = json_dumps_compact(self._msh.marshal_obj(o, ty))
         
     | 
| 
       36 
59 
     | 
    
         
             
                    d = j.encode('utf-8')
         
     | 
| 
       37 
60 
     | 
    
         | 
| 
       38 
61 
     | 
    
         
             
                    self._output.write(struct.pack('<I', len(d)))
         
     | 
| 
       39 
62 
     | 
    
         
             
                    self._output.write(d)
         
     | 
| 
       40 
     | 
    
         
            -
                    self._output. 
     | 
| 
      
 63 
     | 
    
         
            +
                    await self._output.drain()
         
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
      
 65 
     | 
    
         
            +
                async def send_obj(self, o: ta.Any, ty: ta.Any = None) -> None:
         
     | 
| 
      
 66 
     | 
    
         
            +
                    async with self._output_lock:
         
     | 
| 
      
 67 
     | 
    
         
            +
                        return await self._send_obj(o, ty)
         
     | 
| 
       41 
68 
     | 
    
         | 
| 
       42 
     | 
    
         
            -
                 
     | 
| 
       43 
     | 
    
         
            -
                    with self._lock:
         
     | 
| 
       44 
     | 
    
         
            -
                        return self._send_obj(o, ty)
         
     | 
| 
      
 69 
     | 
    
         
            +
                #
         
     | 
| 
       45 
70 
     | 
    
         | 
| 
       46 
     | 
    
         
            -
                def _recv_obj(self, ty: ta.Type[T]) -> ta.Optional[T]:
         
     | 
| 
       47 
     | 
    
         
            -
                    d = self._input.read(4)
         
     | 
| 
      
 71 
     | 
    
         
            +
                async def _recv_obj(self, ty: ta.Type[T]) -> ta.Optional[T]:
         
     | 
| 
      
 72 
     | 
    
         
            +
                    d = await self._input.read(4)
         
     | 
| 
       48 
73 
     | 
    
         
             
                    if not d:
         
     | 
| 
       49 
74 
     | 
    
         
             
                        return None
         
     | 
| 
       50 
75 
     | 
    
         
             
                    if len(d) != 4:
         
     | 
| 
       51 
76 
     | 
    
         
             
                        raise EOFError
         
     | 
| 
       52 
77 
     | 
    
         | 
| 
       53 
78 
     | 
    
         
             
                    sz = struct.unpack('<I', d)[0]
         
     | 
| 
       54 
     | 
    
         
            -
                    d = self._input.read(sz)
         
     | 
| 
      
 79 
     | 
    
         
            +
                    d = await self._input.read(sz)
         
     | 
| 
       55 
80 
     | 
    
         
             
                    if len(d) != sz:
         
     | 
| 
       56 
81 
     | 
    
         
             
                        raise EOFError
         
     | 
| 
       57 
82 
     | 
    
         | 
| 
       58 
83 
     | 
    
         
             
                    j = json.loads(d.decode('utf-8'))
         
     | 
| 
       59 
84 
     | 
    
         
             
                    return self._msh.unmarshal_obj(j, ty)
         
     | 
| 
       60 
85 
     | 
    
         | 
| 
       61 
     | 
    
         
            -
                def recv_obj(self, ty: ta.Type[T]) -> ta.Optional[T]:
         
     | 
| 
       62 
     | 
    
         
            -
                    with self. 
     | 
| 
       63 
     | 
    
         
            -
                        return self._recv_obj(ty)
         
     | 
| 
      
 86 
     | 
    
         
            +
                async def recv_obj(self, ty: ta.Type[T]) -> ta.Optional[T]:
         
     | 
| 
      
 87 
     | 
    
         
            +
                    async with self._input_lock:
         
     | 
| 
      
 88 
     | 
    
         
            +
                        return await self._recv_obj(ty)
         
     | 
    
        ominfra/manage/remote/config.py
    CHANGED
    
    | 
         @@ -9,4 +9,14 @@ from omlish.lite.pycharm import PycharmRemoteDebug 
     | 
|
| 
       9 
9 
     | 
    
         
             
            class RemoteConfig:
         
     | 
| 
       10 
10 
     | 
    
         
             
                payload_file: ta.Optional[str] = None
         
     | 
| 
       11 
11 
     | 
    
         | 
| 
      
 12 
     | 
    
         
            +
                set_pgid: bool = True
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
                deathsig: ta.Optional[str] = 'KILL'
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
       12 
16 
     | 
    
         
             
                pycharm_remote_debug: ta.Optional[PycharmRemoteDebug] = None
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                forward_logging: bool = True
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                timebomb_delay_s: ta.Optional[float] = 60 * 60.
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                heartbeat_interval_s: float = 3.
         
     |