ominfra 0.0.0.dev144__py3-none-any.whl → 0.0.0.dev146__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/commands/execution.py +1 -1
- ominfra/manage/commands/inject.py +7 -3
- ominfra/manage/commands/interp.py +39 -0
- ominfra/manage/commands/subprocess.py +2 -2
- ominfra/manage/deploy/command.py +4 -0
- ominfra/manage/deploy/paths.py +180 -0
- ominfra/manage/main.py +16 -9
- ominfra/manage/remote/channel.py +13 -2
- ominfra/manage/remote/execution.py +66 -9
- ominfra/scripts/journald2aws.py +73 -53
- ominfra/scripts/manage.py +2272 -293
- ominfra/scripts/supervisor.py +114 -94
- ominfra/supervisor/dispatchers.py +3 -3
- ominfra/supervisor/http.py +6 -6
- ominfra/supervisor/inject.py +12 -12
- ominfra/supervisor/io.py +2 -2
- ominfra/supervisor/spawningimpl.py +2 -2
- ominfra/supervisor/supervisor.py +2 -2
- ominfra/supervisor/types.py +2 -2
- {ominfra-0.0.0.dev144.dist-info → ominfra-0.0.0.dev146.dist-info}/METADATA +3 -3
- {ominfra-0.0.0.dev144.dist-info → ominfra-0.0.0.dev146.dist-info}/RECORD +25 -23
- {ominfra-0.0.0.dev144.dist-info → ominfra-0.0.0.dev146.dist-info}/LICENSE +0 -0
- {ominfra-0.0.0.dev144.dist-info → ominfra-0.0.0.dev146.dist-info}/WHEEL +0 -0
- {ominfra-0.0.0.dev144.dist-info → ominfra-0.0.0.dev146.dist-info}/entry_points.txt +0 -0
- {ominfra-0.0.0.dev144.dist-info → ominfra-0.0.0.dev146.dist-info}/top_level.txt +0 -0
@@ -8,7 +8,7 @@ from .base import CommandExecutor
|
|
8
8
|
CommandExecutorMap = ta.NewType('CommandExecutorMap', ta.Mapping[ta.Type[Command], CommandExecutor])
|
9
9
|
|
10
10
|
|
11
|
-
class
|
11
|
+
class LocalCommandExecutor(CommandExecutor):
|
12
12
|
def __init__(
|
13
13
|
self,
|
14
14
|
*,
|
@@ -18,8 +18,10 @@ from .base import CommandNameMap
|
|
18
18
|
from .base import CommandRegistration
|
19
19
|
from .base import CommandRegistrations
|
20
20
|
from .base import build_command_name_map
|
21
|
-
from .execution import CommandExecutionService
|
22
21
|
from .execution import CommandExecutorMap
|
22
|
+
from .execution import LocalCommandExecutor
|
23
|
+
from .interp import InterpCommand
|
24
|
+
from .interp import InterpCommandExecutor
|
23
25
|
from .marshal import install_command_marshaling
|
24
26
|
from .subprocess import SubprocessCommand
|
25
27
|
from .subprocess import SubprocessCommandExecutor
|
@@ -106,14 +108,16 @@ def bind_commands(
|
|
106
108
|
lst.extend([
|
107
109
|
inj.bind(provide_command_executor_map, singleton=True),
|
108
110
|
|
109
|
-
inj.bind(
|
110
|
-
inj.bind(CommandExecutor, to_key=CommandExecutionService),
|
111
|
+
inj.bind(LocalCommandExecutor, singleton=True, eager=main_config.debug),
|
111
112
|
])
|
112
113
|
|
113
114
|
#
|
114
115
|
|
116
|
+
command_cls: ta.Any
|
117
|
+
executor_cls: ta.Any
|
115
118
|
for command_cls, executor_cls in [
|
116
119
|
(SubprocessCommand, SubprocessCommandExecutor),
|
120
|
+
(InterpCommand, InterpCommandExecutor),
|
117
121
|
]:
|
118
122
|
lst.append(bind_command(command_cls, executor_cls))
|
119
123
|
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
import dataclasses as dc
|
3
|
+
|
4
|
+
from omdev.interp.resolvers import DEFAULT_INTERP_RESOLVER
|
5
|
+
from omdev.interp.types import InterpOpts
|
6
|
+
from omdev.interp.types import InterpSpecifier
|
7
|
+
from omlish.lite.check import check_not_none
|
8
|
+
|
9
|
+
from ..commands.base import Command
|
10
|
+
from ..commands.base import CommandExecutor
|
11
|
+
|
12
|
+
|
13
|
+
##
|
14
|
+
|
15
|
+
|
16
|
+
@dc.dataclass(frozen=True)
|
17
|
+
class InterpCommand(Command['InterpCommand.Output']):
|
18
|
+
spec: str
|
19
|
+
install: bool = False
|
20
|
+
|
21
|
+
@dc.dataclass(frozen=True)
|
22
|
+
class Output(Command.Output):
|
23
|
+
exe: str
|
24
|
+
version: str
|
25
|
+
opts: InterpOpts
|
26
|
+
|
27
|
+
|
28
|
+
##
|
29
|
+
|
30
|
+
|
31
|
+
class InterpCommandExecutor(CommandExecutor[InterpCommand, InterpCommand.Output]):
|
32
|
+
def execute(self, cmd: InterpCommand) -> InterpCommand.Output:
|
33
|
+
i = InterpSpecifier.parse(check_not_none(cmd.spec))
|
34
|
+
o = check_not_none(DEFAULT_INTERP_RESOLVER.resolve(i, install=cmd.install))
|
35
|
+
return InterpCommand.Output(
|
36
|
+
exe=o.exe,
|
37
|
+
version=str(o.version.version),
|
38
|
+
opts=o.version.opts,
|
39
|
+
)
|
@@ -5,6 +5,7 @@ import subprocess
|
|
5
5
|
import time
|
6
6
|
import typing as ta
|
7
7
|
|
8
|
+
from omlish.lite.check import check_not_isinstance
|
8
9
|
from omlish.lite.subprocesses import SUBPROCESS_CHANNEL_OPTION_VALUES
|
9
10
|
from omlish.lite.subprocesses import SubprocessChannelOption
|
10
11
|
from omlish.lite.subprocesses import subprocess_maybe_shell_wrap_exec
|
@@ -31,8 +32,7 @@ class SubprocessCommand(Command['SubprocessCommand.Output']):
|
|
31
32
|
timeout: ta.Optional[float] = None
|
32
33
|
|
33
34
|
def __post_init__(self) -> None:
|
34
|
-
|
35
|
-
raise TypeError(self.cmd)
|
35
|
+
check_not_isinstance(self.cmd, str)
|
36
36
|
|
37
37
|
@dc.dataclass(frozen=True)
|
38
38
|
class Output(Command.Output):
|
ominfra/manage/deploy/command.py
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# ruff: noqa: UP006 UP007
|
2
2
|
import dataclasses as dc
|
3
3
|
|
4
|
+
from omlish.lite.logs import log
|
5
|
+
|
4
6
|
from ..commands.base import Command
|
5
7
|
from ..commands.base import CommandExecutor
|
6
8
|
|
@@ -20,4 +22,6 @@ class DeployCommand(Command['DeployCommand.Output']):
|
|
20
22
|
|
21
23
|
class DeployCommandExecutor(CommandExecutor[DeployCommand, DeployCommand.Output]):
|
22
24
|
def execute(self, cmd: DeployCommand) -> DeployCommand.Output:
|
25
|
+
log.info('Deploying!')
|
26
|
+
|
23
27
|
return DeployCommand.Output()
|
@@ -0,0 +1,180 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
"""
|
3
|
+
~deploy
|
4
|
+
deploy.pid (flock)
|
5
|
+
/app
|
6
|
+
/<appspec> - shallow clone
|
7
|
+
/conf
|
8
|
+
/env
|
9
|
+
<appspec>.env
|
10
|
+
/nginx
|
11
|
+
<appspec>.conf
|
12
|
+
/supervisor
|
13
|
+
<appspec>.conf
|
14
|
+
/venv
|
15
|
+
/<appspec>
|
16
|
+
|
17
|
+
?
|
18
|
+
/logs
|
19
|
+
/wrmsr--omlish--<spec>
|
20
|
+
|
21
|
+
spec = <name>--<rev>--<when>
|
22
|
+
|
23
|
+
==
|
24
|
+
|
25
|
+
for dn in [
|
26
|
+
'app',
|
27
|
+
'conf',
|
28
|
+
'conf/env',
|
29
|
+
'conf/nginx',
|
30
|
+
'conf/supervisor',
|
31
|
+
'venv',
|
32
|
+
]:
|
33
|
+
"""
|
34
|
+
import abc
|
35
|
+
import dataclasses as dc
|
36
|
+
import os.path
|
37
|
+
import typing as ta
|
38
|
+
|
39
|
+
from omlish.lite.check import check_equal
|
40
|
+
from omlish.lite.check import check_non_empty
|
41
|
+
from omlish.lite.check import check_non_empty_str
|
42
|
+
from omlish.lite.check import check_not_in
|
43
|
+
|
44
|
+
|
45
|
+
DeployPathKind = ta.Literal['dir', 'file'] # ta.TypeAlias
|
46
|
+
|
47
|
+
|
48
|
+
##
|
49
|
+
|
50
|
+
|
51
|
+
DEPLOY_PATH_SPEC_PLACEHOLDER = '@'
|
52
|
+
|
53
|
+
|
54
|
+
@dc.dataclass(frozen=True)
|
55
|
+
class DeployPathPart(abc.ABC): # noqa
|
56
|
+
@property
|
57
|
+
@abc.abstractmethod
|
58
|
+
def kind(self) -> DeployPathKind:
|
59
|
+
raise NotImplementedError
|
60
|
+
|
61
|
+
@abc.abstractmethod
|
62
|
+
def render(self) -> str:
|
63
|
+
raise NotImplementedError
|
64
|
+
|
65
|
+
|
66
|
+
#
|
67
|
+
|
68
|
+
|
69
|
+
class DeployPathDir(DeployPathPart, abc.ABC):
|
70
|
+
@property
|
71
|
+
def kind(self) -> DeployPathKind:
|
72
|
+
return 'dir'
|
73
|
+
|
74
|
+
@classmethod
|
75
|
+
def parse(cls, s: str) -> 'DeployPathDir':
|
76
|
+
if DEPLOY_PATH_SPEC_PLACEHOLDER in s:
|
77
|
+
check_equal(s, DEPLOY_PATH_SPEC_PLACEHOLDER)
|
78
|
+
return SpecDeployPathDir()
|
79
|
+
else:
|
80
|
+
return ConstDeployPathDir(s)
|
81
|
+
|
82
|
+
|
83
|
+
class DeployPathFile(DeployPathPart, abc.ABC):
|
84
|
+
@property
|
85
|
+
def kind(self) -> DeployPathKind:
|
86
|
+
return 'file'
|
87
|
+
|
88
|
+
@classmethod
|
89
|
+
def parse(cls, s: str) -> 'DeployPathFile':
|
90
|
+
if DEPLOY_PATH_SPEC_PLACEHOLDER in s:
|
91
|
+
check_equal(s[0], DEPLOY_PATH_SPEC_PLACEHOLDER)
|
92
|
+
return SpecDeployPathFile(s[1:])
|
93
|
+
else:
|
94
|
+
return ConstDeployPathFile(s)
|
95
|
+
|
96
|
+
|
97
|
+
#
|
98
|
+
|
99
|
+
|
100
|
+
@dc.dataclass(frozen=True)
|
101
|
+
class ConstDeployPathPart(DeployPathPart, abc.ABC):
|
102
|
+
name: str
|
103
|
+
|
104
|
+
def __post_init__(self) -> None:
|
105
|
+
check_non_empty_str(self.name)
|
106
|
+
check_not_in('/', self.name)
|
107
|
+
check_not_in(DEPLOY_PATH_SPEC_PLACEHOLDER, self.name)
|
108
|
+
|
109
|
+
def render(self) -> str:
|
110
|
+
return self.name
|
111
|
+
|
112
|
+
|
113
|
+
class ConstDeployPathDir(ConstDeployPathPart, DeployPathDir):
|
114
|
+
pass
|
115
|
+
|
116
|
+
|
117
|
+
class ConstDeployPathFile(ConstDeployPathPart, DeployPathFile):
|
118
|
+
pass
|
119
|
+
|
120
|
+
|
121
|
+
#
|
122
|
+
|
123
|
+
|
124
|
+
class SpecDeployPathPart(DeployPathPart, abc.ABC):
|
125
|
+
pass
|
126
|
+
|
127
|
+
|
128
|
+
class SpecDeployPathDir(SpecDeployPathPart, DeployPathDir):
|
129
|
+
def render(self) -> str:
|
130
|
+
return DEPLOY_PATH_SPEC_PLACEHOLDER
|
131
|
+
|
132
|
+
|
133
|
+
@dc.dataclass(frozen=True)
|
134
|
+
class SpecDeployPathFile(SpecDeployPathPart, DeployPathFile):
|
135
|
+
suffix: str
|
136
|
+
|
137
|
+
def __post_init__(self) -> None:
|
138
|
+
check_non_empty_str(self.suffix)
|
139
|
+
check_not_in('/', self.suffix)
|
140
|
+
check_not_in(DEPLOY_PATH_SPEC_PLACEHOLDER, self.suffix)
|
141
|
+
|
142
|
+
def render(self) -> str:
|
143
|
+
return DEPLOY_PATH_SPEC_PLACEHOLDER + self.suffix
|
144
|
+
|
145
|
+
|
146
|
+
##
|
147
|
+
|
148
|
+
|
149
|
+
@dc.dataclass(frozen=True)
|
150
|
+
class DeployPath:
|
151
|
+
parts: ta.Sequence[DeployPathPart]
|
152
|
+
|
153
|
+
def __post_init__(self) -> None:
|
154
|
+
check_non_empty(self.parts)
|
155
|
+
for p in self.parts[:-1]:
|
156
|
+
check_equal(p.kind, 'dir')
|
157
|
+
|
158
|
+
@property
|
159
|
+
def kind(self) -> ta.Literal['file', 'dir']:
|
160
|
+
return self.parts[-1].kind
|
161
|
+
|
162
|
+
def render(self) -> str:
|
163
|
+
return os.path.join( # noqa
|
164
|
+
*[p.render() for p in self.parts],
|
165
|
+
*([''] if self.kind == 'dir' else []),
|
166
|
+
)
|
167
|
+
|
168
|
+
@classmethod
|
169
|
+
def parse(cls, s: str) -> 'DeployPath':
|
170
|
+
tail_parse: ta.Callable[[str], DeployPathPart]
|
171
|
+
if s.endswith('/'):
|
172
|
+
tail_parse = DeployPathDir.parse
|
173
|
+
s = s[:-1]
|
174
|
+
else:
|
175
|
+
tail_parse = DeployPathFile.parse
|
176
|
+
ps = check_non_empty_str(s).split('/')
|
177
|
+
return cls([
|
178
|
+
*([DeployPathDir.parse(p) for p in ps[:-1]] if len(ps) > 1 else []),
|
179
|
+
tail_parse(ps[-1]),
|
180
|
+
])
|
ominfra/manage/main.py
CHANGED
@@ -6,6 +6,7 @@ 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
8
|
import contextlib
|
9
|
+
import json
|
9
10
|
import typing as ta
|
10
11
|
|
11
12
|
from omlish.lite.logs import log # noqa
|
@@ -17,9 +18,8 @@ from .bootstrap import MainBootstrap
|
|
17
18
|
from .bootstrap_ import main_bootstrap
|
18
19
|
from .commands.base import Command
|
19
20
|
from .commands.base import CommandExecutor
|
20
|
-
from .commands.
|
21
|
+
from .commands.execution import LocalCommandExecutor
|
21
22
|
from .config import MainConfig
|
22
|
-
from .deploy.command import DeployCommand
|
23
23
|
from .remote.config import RemoteConfig
|
24
24
|
from .remote.execution import RemoteExecution
|
25
25
|
from .remote.spawning import RemoteSpawning
|
@@ -77,12 +77,15 @@ def _main() -> None:
|
|
77
77
|
|
78
78
|
#
|
79
79
|
|
80
|
+
msh = injector[ObjMarshalerManager]
|
81
|
+
|
80
82
|
cmds: ta.List[Command] = []
|
83
|
+
cmd: Command
|
81
84
|
for c in args.command:
|
82
|
-
if c
|
83
|
-
|
84
|
-
|
85
|
-
|
85
|
+
if not c.startswith('{'):
|
86
|
+
c = json.dumps({c: {}})
|
87
|
+
cmd = msh.unmarshal_obj(json.loads(c), Command)
|
88
|
+
cmds.append(cmd)
|
86
89
|
|
87
90
|
#
|
88
91
|
|
@@ -90,7 +93,7 @@ def _main() -> None:
|
|
90
93
|
ce: CommandExecutor
|
91
94
|
|
92
95
|
if args.local:
|
93
|
-
ce = injector[
|
96
|
+
ce = injector[LocalCommandExecutor]
|
94
97
|
|
95
98
|
else:
|
96
99
|
tgt = RemoteSpawning.Target(
|
@@ -102,9 +105,13 @@ def _main() -> None:
|
|
102
105
|
ce = es.enter_context(injector[RemoteExecution].connect(tgt, bs)) # noqa
|
103
106
|
|
104
107
|
for cmd in cmds:
|
105
|
-
r = ce.try_execute(
|
108
|
+
r = ce.try_execute(
|
109
|
+
cmd,
|
110
|
+
log=log,
|
111
|
+
omit_exc_object=True,
|
112
|
+
)
|
106
113
|
|
107
|
-
print(
|
114
|
+
print(msh.marshal_obj(r, opts=ObjMarshalOptions(raw_bytes=True)))
|
108
115
|
|
109
116
|
|
110
117
|
if __name__ == '__main__':
|
ominfra/manage/remote/channel.py
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# ruff: noqa: UP006 UP007
|
2
2
|
import json
|
3
3
|
import struct
|
4
|
+
import threading
|
4
5
|
import typing as ta
|
5
6
|
|
6
7
|
from omlish.lite.json import json_dumps_compact
|
@@ -25,10 +26,12 @@ class RemoteChannel:
|
|
25
26
|
self._output = output
|
26
27
|
self._msh = msh
|
27
28
|
|
29
|
+
self._lock = threading.RLock()
|
30
|
+
|
28
31
|
def set_marshaler(self, msh: ObjMarshalerManager) -> None:
|
29
32
|
self._msh = msh
|
30
33
|
|
31
|
-
def
|
34
|
+
def _send_obj(self, o: ta.Any, ty: ta.Any = None) -> None:
|
32
35
|
j = json_dumps_compact(self._msh.marshal_obj(o, ty))
|
33
36
|
d = j.encode('utf-8')
|
34
37
|
|
@@ -36,7 +39,11 @@ class RemoteChannel:
|
|
36
39
|
self._output.write(d)
|
37
40
|
self._output.flush()
|
38
41
|
|
39
|
-
def
|
42
|
+
def send_obj(self, o: ta.Any, ty: ta.Any = None) -> None:
|
43
|
+
with self._lock:
|
44
|
+
return self._send_obj(o, ty)
|
45
|
+
|
46
|
+
def _recv_obj(self, ty: ta.Type[T]) -> ta.Optional[T]:
|
40
47
|
d = self._input.read(4)
|
41
48
|
if not d:
|
42
49
|
return None
|
@@ -50,3 +57,7 @@ class RemoteChannel:
|
|
50
57
|
|
51
58
|
j = json.loads(d.decode('utf-8'))
|
52
59
|
return self._msh.unmarshal_obj(j, ty)
|
60
|
+
|
61
|
+
def recv_obj(self, ty: ta.Type[T]) -> ta.Optional[T]:
|
62
|
+
with self._lock:
|
63
|
+
return self._recv_obj(ty)
|
@@ -2,6 +2,7 @@
|
|
2
2
|
import contextlib
|
3
3
|
import dataclasses as dc
|
4
4
|
import logging
|
5
|
+
import threading
|
5
6
|
import typing as ta
|
6
7
|
|
7
8
|
from omlish.lite.cached import cached_nullary
|
@@ -20,6 +21,7 @@ from ..commands.base import CommandException
|
|
20
21
|
from ..commands.base import CommandExecutor
|
21
22
|
from ..commands.base import CommandOutputOrException
|
22
23
|
from ..commands.base import CommandOutputOrExceptionData
|
24
|
+
from ..commands.execution import LocalCommandExecutor
|
23
25
|
from .channel import RemoteChannel
|
24
26
|
from .payload import RemoteExecutionPayloadFile
|
25
27
|
from .payload import get_remote_payload_src
|
@@ -35,6 +37,32 @@ else:
|
|
35
37
|
##
|
36
38
|
|
37
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
|
+
@dc.dataclass(frozen=True)
|
51
|
+
class _RemoteExecutionRequest:
|
52
|
+
c: Command
|
53
|
+
|
54
|
+
|
55
|
+
@dc.dataclass(frozen=True)
|
56
|
+
class _RemoteExecutionLog:
|
57
|
+
s: str
|
58
|
+
|
59
|
+
|
60
|
+
@dc.dataclass(frozen=True)
|
61
|
+
class _RemoteExecutionResponse:
|
62
|
+
r: ta.Optional[CommandOutputOrExceptionData] = None
|
63
|
+
l: ta.Optional[_RemoteExecutionLog] = None
|
64
|
+
|
65
|
+
|
38
66
|
def _remote_execution_main() -> None:
|
39
67
|
rt = pyremote_bootstrap_finalize() # noqa
|
40
68
|
|
@@ -52,20 +80,44 @@ def _remote_execution_main() -> None:
|
|
52
80
|
|
53
81
|
chan.set_marshaler(injector[ObjMarshalerManager])
|
54
82
|
|
55
|
-
|
83
|
+
#
|
84
|
+
|
85
|
+
log_lock = threading.RLock()
|
86
|
+
send_logs = False
|
87
|
+
|
88
|
+
def log_fn(s: str) -> None:
|
89
|
+
with log_lock:
|
90
|
+
if send_logs:
|
91
|
+
chan.send_obj(_RemoteExecutionResponse(l=_RemoteExecutionLog(s)))
|
92
|
+
|
93
|
+
log_handler = _RemoteExecutionLogHandler(log_fn)
|
94
|
+
logging.root.addHandler(log_handler)
|
95
|
+
|
96
|
+
#
|
97
|
+
|
98
|
+
ce = injector[LocalCommandExecutor]
|
56
99
|
|
57
100
|
while True:
|
58
|
-
|
59
|
-
if
|
101
|
+
req = chan.recv_obj(_RemoteExecutionRequest)
|
102
|
+
if req is None:
|
60
103
|
break
|
61
104
|
|
105
|
+
with log_lock:
|
106
|
+
send_logs = True
|
107
|
+
|
62
108
|
r = ce.try_execute(
|
63
|
-
|
109
|
+
req.c,
|
64
110
|
log=log,
|
65
111
|
omit_exc_object=True,
|
66
112
|
)
|
67
113
|
|
68
|
-
|
114
|
+
with log_lock:
|
115
|
+
send_logs = False
|
116
|
+
|
117
|
+
chan.send_obj(_RemoteExecutionResponse(r=CommandOutputOrExceptionData(
|
118
|
+
output=r.output,
|
119
|
+
exception=r.exception,
|
120
|
+
)))
|
69
121
|
|
70
122
|
|
71
123
|
##
|
@@ -83,12 +135,17 @@ class RemoteCommandExecutor(CommandExecutor):
|
|
83
135
|
self._chan = chan
|
84
136
|
|
85
137
|
def _remote_execute(self, cmd: Command) -> CommandOutputOrException:
|
86
|
-
self._chan.send_obj(cmd
|
138
|
+
self._chan.send_obj(_RemoteExecutionRequest(cmd))
|
139
|
+
|
140
|
+
while True:
|
141
|
+
if (r := self._chan.recv_obj(_RemoteExecutionResponse)) is None:
|
142
|
+
raise EOFError
|
87
143
|
|
88
|
-
|
89
|
-
|
144
|
+
if r.l is not None:
|
145
|
+
log.info(r.l.s)
|
90
146
|
|
91
|
-
|
147
|
+
if r.r is not None:
|
148
|
+
return r.r
|
92
149
|
|
93
150
|
# @ta.override
|
94
151
|
def execute(self, cmd: Command) -> Command.Output:
|