ominfra 0.0.0.dev142__py3-none-any.whl → 0.0.0.dev143__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -10,4 +10,14 @@ Jobs:
10
10
  - system service manager - systemd / supervisor
11
11
  - users
12
12
  - firewall
13
+
14
+ Deploy:
15
+ - User
16
+ - Dirs
17
+ - GlobalNginx
18
+ - GlobalSupervisor
19
+ - Repo
20
+ - Venv
21
+ - Supervisor
22
+ - Nginx
13
23
  """
@@ -0,0 +1,4 @@
1
+ if __name__ == '__main__':
2
+ from .main import _main # noqa
3
+
4
+ _main()
@@ -0,0 +1,11 @@
1
+ import dataclasses as dc
2
+
3
+ from .config import MainConfig
4
+ from .remote.config import RemoteConfig
5
+
6
+
7
+ @dc.dataclass(frozen=True)
8
+ class MainBootstrap:
9
+ main_config: MainConfig = MainConfig()
10
+
11
+ remote_config: RemoteConfig = RemoteConfig()
@@ -0,0 +1,18 @@
1
+ from omlish.lite.inject import Injector
2
+ from omlish.lite.inject import inj
3
+ from omlish.lite.logs import configure_standard_logging
4
+
5
+ from .bootstrap import MainBootstrap
6
+ from .inject import bind_main
7
+
8
+
9
+ def main_bootstrap(bs: MainBootstrap) -> Injector:
10
+ if (log_level := bs.main_config.log_level) is not None:
11
+ configure_standard_logging(log_level)
12
+
13
+ injector = inj.create_injector(bind_main( # noqa
14
+ main_config=bs.main_config,
15
+ remote_config=bs.remote_config,
16
+ ))
17
+
18
+ return injector
@@ -1,8 +1,13 @@
1
1
  # ruff: noqa: UP006 UP007
2
2
  import abc
3
3
  import dataclasses as dc
4
+ import logging
5
+ import traceback
4
6
  import typing as ta
5
7
 
8
+ from omlish.lite.check import check_isinstance
9
+ from omlish.lite.strings import snake_case
10
+
6
11
 
7
12
  CommandT = ta.TypeVar('CommandT', bound='Command')
8
13
  CommandOutputT = ta.TypeVar('CommandOutputT', bound='Command.Output')
@@ -17,11 +22,138 @@ class Command(abc.ABC, ta.Generic[CommandOutputT]):
17
22
  class Output(abc.ABC): # noqa
18
23
  pass
19
24
 
25
+ @ta.final
26
+ def execute(self, executor: 'CommandExecutor') -> CommandOutputT:
27
+ return check_isinstance(executor.execute(self), self.Output) # type: ignore[return-value]
28
+
20
29
 
21
30
  ##
22
31
 
23
32
 
33
+ @dc.dataclass(frozen=True)
34
+ class CommandException:
35
+ name: str
36
+ repr: str
37
+
38
+ traceback: ta.Optional[str] = None
39
+
40
+ exc: ta.Optional[ta.Any] = None # Exception
41
+
42
+ cmd: ta.Optional[Command] = None
43
+
44
+ @classmethod
45
+ def of(
46
+ cls,
47
+ exc: Exception,
48
+ *,
49
+ omit_exc_object: bool = False,
50
+
51
+ cmd: ta.Optional[Command] = None,
52
+ ) -> 'CommandException':
53
+ return CommandException(
54
+ name=type(exc).__qualname__,
55
+ repr=repr(exc),
56
+
57
+ traceback=(
58
+ ''.join(traceback.format_tb(exc.__traceback__))
59
+ if getattr(exc, '__traceback__', None) is not None else None
60
+ ),
61
+
62
+ exc=None if omit_exc_object else exc,
63
+
64
+ cmd=cmd,
65
+ )
66
+
67
+
68
+ class CommandOutputOrException(abc.ABC, ta.Generic[CommandOutputT]):
69
+ @property
70
+ @abc.abstractmethod
71
+ def output(self) -> ta.Optional[CommandOutputT]:
72
+ raise NotImplementedError
73
+
74
+ @property
75
+ @abc.abstractmethod
76
+ def exception(self) -> ta.Optional[CommandException]:
77
+ raise NotImplementedError
78
+
79
+
80
+ @dc.dataclass(frozen=True)
81
+ class CommandOutputOrExceptionData(CommandOutputOrException):
82
+ output: ta.Optional[Command.Output] = None
83
+ exception: ta.Optional[CommandException] = None
84
+
85
+
24
86
  class CommandExecutor(abc.ABC, ta.Generic[CommandT, CommandOutputT]):
25
87
  @abc.abstractmethod
26
- def execute(self, i: CommandT) -> CommandOutputT:
88
+ def execute(self, cmd: CommandT) -> CommandOutputT:
27
89
  raise NotImplementedError
90
+
91
+ def try_execute(
92
+ self,
93
+ cmd: CommandT,
94
+ *,
95
+ log: ta.Optional[logging.Logger] = None,
96
+ omit_exc_object: bool = False,
97
+ ) -> CommandOutputOrException[CommandOutputT]:
98
+ try:
99
+ o = self.execute(cmd)
100
+
101
+ except Exception as e: # noqa
102
+ if log is not None:
103
+ log.exception('Exception executing command: %r', type(cmd))
104
+
105
+ return CommandOutputOrExceptionData(exception=CommandException.of(
106
+ e,
107
+ omit_exc_object=omit_exc_object,
108
+ cmd=cmd,
109
+ ))
110
+
111
+ else:
112
+ return CommandOutputOrExceptionData(output=o)
113
+
114
+
115
+ ##
116
+
117
+
118
+ @dc.dataclass(frozen=True)
119
+ class CommandRegistration:
120
+ command_cls: ta.Type[Command]
121
+
122
+ name: ta.Optional[str] = None
123
+
124
+ @property
125
+ def name_or_default(self) -> str:
126
+ if not (cls_name := self.command_cls.__name__).endswith('Command'):
127
+ raise NameError(cls_name)
128
+ return snake_case(cls_name[:-len('Command')])
129
+
130
+
131
+ CommandRegistrations = ta.NewType('CommandRegistrations', ta.Sequence[CommandRegistration])
132
+
133
+
134
+ ##
135
+
136
+
137
+ @dc.dataclass(frozen=True)
138
+ class CommandExecutorRegistration:
139
+ command_cls: ta.Type[Command]
140
+ executor_cls: ta.Type[CommandExecutor]
141
+
142
+
143
+ CommandExecutorRegistrations = ta.NewType('CommandExecutorRegistrations', ta.Sequence[CommandExecutorRegistration])
144
+
145
+
146
+ ##
147
+
148
+
149
+ CommandNameMap = ta.NewType('CommandNameMap', ta.Mapping[str, ta.Type[Command]])
150
+
151
+
152
+ def build_command_name_map(crs: CommandRegistrations) -> CommandNameMap:
153
+ dct: ta.Dict[str, ta.Type[Command]] = {}
154
+ cr: CommandRegistration
155
+ for cr in crs:
156
+ if (name := cr.name_or_default) in dct:
157
+ raise NameError(name)
158
+ dct[name] = cr.command_cls
159
+ return CommandNameMap(dct)
@@ -0,0 +1,23 @@
1
+ # ruff: noqa: UP006 UP007
2
+ import typing as ta
3
+
4
+ from .base import Command
5
+ from .base import CommandExecutor
6
+
7
+
8
+ CommandExecutorMap = ta.NewType('CommandExecutorMap', ta.Mapping[ta.Type[Command], CommandExecutor])
9
+
10
+
11
+ class CommandExecutionService(CommandExecutor):
12
+ def __init__(
13
+ self,
14
+ *,
15
+ command_executors: CommandExecutorMap,
16
+ ) -> None:
17
+ super().__init__()
18
+
19
+ self._command_executors = command_executors
20
+
21
+ def execute(self, cmd: Command) -> Command.Output:
22
+ ce: CommandExecutor = self._command_executors[type(cmd)]
23
+ return ce.execute(cmd)
@@ -0,0 +1,122 @@
1
+ # ruff: noqa: UP006 UP007
2
+ import dataclasses as dc
3
+ import functools
4
+ import typing as ta
5
+
6
+ from omlish.lite.inject import Injector
7
+ from omlish.lite.inject import InjectorBindingOrBindings
8
+ from omlish.lite.inject import InjectorBindings
9
+ from omlish.lite.inject import inj
10
+
11
+ from ..config import MainConfig
12
+ from ..marshal import ObjMarshalerInstaller
13
+ from .base import Command
14
+ from .base import CommandExecutor
15
+ from .base import CommandExecutorRegistration
16
+ from .base import CommandExecutorRegistrations
17
+ from .base import CommandNameMap
18
+ from .base import CommandRegistration
19
+ from .base import CommandRegistrations
20
+ from .base import build_command_name_map
21
+ from .execution import CommandExecutionService
22
+ from .execution import CommandExecutorMap
23
+ from .marshal import install_command_marshaling
24
+ from .subprocess import SubprocessCommand
25
+ from .subprocess import SubprocessCommandExecutor
26
+
27
+
28
+ ##
29
+
30
+
31
+ def bind_command(
32
+ command_cls: ta.Type[Command],
33
+ executor_cls: ta.Optional[ta.Type[CommandExecutor]],
34
+ ) -> InjectorBindings:
35
+ lst: ta.List[InjectorBindingOrBindings] = [
36
+ inj.bind(CommandRegistration(command_cls), array=True),
37
+ ]
38
+
39
+ if executor_cls is not None:
40
+ lst.extend([
41
+ inj.bind(executor_cls, singleton=True),
42
+ inj.bind(CommandExecutorRegistration(command_cls, executor_cls), array=True),
43
+ ])
44
+
45
+ return inj.as_bindings(*lst)
46
+
47
+
48
+ ##
49
+
50
+
51
+ @dc.dataclass(frozen=True)
52
+ class _FactoryCommandExecutor(CommandExecutor):
53
+ factory: ta.Callable[[], CommandExecutor]
54
+
55
+ def execute(self, i: Command) -> Command.Output:
56
+ return self.factory().execute(i)
57
+
58
+
59
+ ##
60
+
61
+
62
+ def bind_commands(
63
+ *,
64
+ main_config: MainConfig,
65
+ ) -> InjectorBindings:
66
+ lst: ta.List[InjectorBindingOrBindings] = [
67
+ inj.bind_array(CommandRegistration),
68
+ inj.bind_array_type(CommandRegistration, CommandRegistrations),
69
+
70
+ inj.bind_array(CommandExecutorRegistration),
71
+ inj.bind_array_type(CommandExecutorRegistration, CommandExecutorRegistrations),
72
+
73
+ inj.bind(build_command_name_map, singleton=True),
74
+ ]
75
+
76
+ #
77
+
78
+ def provide_obj_marshaler_installer(cmds: CommandNameMap) -> ObjMarshalerInstaller:
79
+ return ObjMarshalerInstaller(functools.partial(install_command_marshaling, cmds))
80
+
81
+ lst.append(inj.bind(provide_obj_marshaler_installer, array=True))
82
+
83
+ #
84
+
85
+ def provide_command_executor_map(
86
+ injector: Injector,
87
+ crs: CommandExecutorRegistrations,
88
+ ) -> CommandExecutorMap:
89
+ dct: ta.Dict[ta.Type[Command], CommandExecutor] = {}
90
+
91
+ cr: CommandExecutorRegistration
92
+ for cr in crs:
93
+ if cr.command_cls in dct:
94
+ raise KeyError(cr.command_cls)
95
+
96
+ factory = functools.partial(injector.provide, cr.executor_cls)
97
+ if main_config.debug:
98
+ ce = factory()
99
+ else:
100
+ ce = _FactoryCommandExecutor(factory)
101
+
102
+ dct[cr.command_cls] = ce
103
+
104
+ return CommandExecutorMap(dct)
105
+
106
+ lst.extend([
107
+ inj.bind(provide_command_executor_map, singleton=True),
108
+
109
+ inj.bind(CommandExecutionService, singleton=True, eager=main_config.debug),
110
+ inj.bind(CommandExecutor, to_key=CommandExecutionService),
111
+ ])
112
+
113
+ #
114
+
115
+ for command_cls, executor_cls in [
116
+ (SubprocessCommand, SubprocessCommandExecutor),
117
+ ]:
118
+ lst.append(bind_command(command_cls, executor_cls))
119
+
120
+ #
121
+
122
+ return inj.as_bindings(*lst)
@@ -0,0 +1,26 @@
1
+ from omlish.lite.marshal import ObjMarshalerManager
2
+ from omlish.lite.marshal import PolymorphicObjMarshaler
3
+
4
+ from .base import Command
5
+ from .base import CommandNameMap
6
+
7
+
8
+ def install_command_marshaling(
9
+ cmds: CommandNameMap,
10
+ msh: ObjMarshalerManager,
11
+ ) -> None:
12
+ for fn in [
13
+ lambda c: c,
14
+ lambda c: c.Output,
15
+ ]:
16
+ msh.register_opj_marshaler(
17
+ fn(Command),
18
+ PolymorphicObjMarshaler.of([
19
+ PolymorphicObjMarshaler.Impl(
20
+ fn(cmd),
21
+ name,
22
+ msh.get_obj_marshaler(fn(cmd)),
23
+ )
24
+ for name, cmd in cmds.items()
25
+ ]),
26
+ )
@@ -0,0 +1,10 @@
1
+ # ruff: noqa: UP006 UP007
2
+ import dataclasses as dc
3
+ import typing as ta
4
+
5
+
6
+ @dc.dataclass(frozen=True)
7
+ class MainConfig:
8
+ log_level: ta.Optional[str] = 'INFO'
9
+
10
+ debug: bool = False
@@ -0,0 +1,55 @@
1
+ # ruff: noqa: UP006 UP007
2
+ import typing as ta
3
+
4
+ from omlish.lite.inject import InjectorBindingOrBindings
5
+ from omlish.lite.inject import InjectorBindings
6
+ from omlish.lite.inject import inj
7
+ from omlish.lite.marshal import ObjMarshalerManager
8
+
9
+ from .commands.inject import bind_commands
10
+ from .config import MainConfig
11
+ from .marshal import ObjMarshalerInstaller
12
+ from .marshal import ObjMarshalerInstallers
13
+ from .remote.config import RemoteConfig
14
+ from .remote.inject import bind_remote
15
+
16
+
17
+ ##
18
+
19
+
20
+ def bind_main(
21
+ *,
22
+ main_config: MainConfig,
23
+ remote_config: RemoteConfig,
24
+ ) -> InjectorBindings:
25
+ lst: ta.List[InjectorBindingOrBindings] = [
26
+ inj.bind(main_config),
27
+
28
+ bind_commands(
29
+ main_config=main_config,
30
+ ),
31
+
32
+ bind_remote(
33
+ remote_config=remote_config,
34
+ ),
35
+ ]
36
+
37
+ #
38
+
39
+ def build_obj_marshaler_manager(insts: ObjMarshalerInstallers) -> ObjMarshalerManager:
40
+ msh = ObjMarshalerManager()
41
+ inst: ObjMarshalerInstaller
42
+ for inst in insts:
43
+ inst.fn(msh)
44
+ return msh
45
+
46
+ lst.extend([
47
+ inj.bind(build_obj_marshaler_manager, singleton=True),
48
+
49
+ inj.bind_array(ObjMarshalerInstaller),
50
+ inj.bind_array_type(ObjMarshalerInstaller, ObjMarshalerInstallers),
51
+ ])
52
+
53
+ #
54
+
55
+ return inj.as_bindings(*lst)
ominfra/manage/main.py CHANGED
@@ -5,72 +5,18 @@
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
- from omlish.lite.marshal import OBJ_MARSHALER_MANAGER
8
+ from omlish.lite.logs import log # noqa
9
+ from omlish.lite.marshal import ObjMarshalOptions
9
10
  from omlish.lite.marshal import ObjMarshalerManager
10
- from omlish.lite.marshal import PolymorphicObjMarshaler
11
+ from omlish.lite.pycharm import PycharmRemoteDebug
11
12
 
12
- from ..pyremote import PyremoteBootstrapDriver
13
- from ..pyremote import PyremoteBootstrapOptions
14
- from ..pyremote import pyremote_bootstrap_finalize
15
- from ..pyremote import pyremote_build_bootstrap_cmd
16
- from .commands.base import Command
13
+ from .bootstrap import MainBootstrap
14
+ from .bootstrap_ import main_bootstrap
17
15
  from .commands.subprocess import SubprocessCommand
18
- from .commands.subprocess import SubprocessCommandExecutor
19
- from .payload import get_payload_src
20
- from .protocol import Channel
21
- from .spawning import PySpawner
22
-
23
-
24
- ##
25
-
26
-
27
- _COMMAND_TYPES = {
28
- 'subprocess': SubprocessCommand,
29
- }
30
-
31
-
32
- ##
33
-
34
-
35
- def register_command_marshaling(msh: ObjMarshalerManager) -> None:
36
- for fn in [
37
- lambda c: c,
38
- lambda c: c.Output,
39
- ]:
40
- msh.register_opj_marshaler(
41
- fn(Command),
42
- PolymorphicObjMarshaler.of([
43
- PolymorphicObjMarshaler.Impl(
44
- fn(cty),
45
- k,
46
- msh.get_obj_marshaler(fn(cty)),
47
- )
48
- for k, cty in _COMMAND_TYPES.items()
49
- ]),
50
- )
51
-
52
-
53
- register_command_marshaling(OBJ_MARSHALER_MANAGER)
54
-
55
-
56
- ##
57
-
58
-
59
- def _remote_main() -> None:
60
- rt = pyremote_bootstrap_finalize() # noqa
61
- chan = Channel(rt.input, rt.output)
62
-
63
- while True:
64
- i = chan.recv_obj(Command)
65
- if i is None:
66
- break
67
-
68
- if isinstance(i, SubprocessCommand):
69
- o = SubprocessCommandExecutor().execute(i) # noqa
70
- else:
71
- raise TypeError(i)
72
-
73
- chan.send_obj(o, Command.Output)
16
+ from .config import MainConfig
17
+ from .remote.config import RemoteConfig
18
+ from .remote.execution import RemoteExecution
19
+ from .remote.spawning import RemoteSpawning
74
20
 
75
21
 
76
22
  ##
@@ -87,50 +33,64 @@ def _main() -> None:
87
33
  parser.add_argument('-q', '--shell-quote', action='store_true')
88
34
  parser.add_argument('--python', default='python3')
89
35
 
36
+ parser.add_argument('--pycharm-debug-port', type=int)
37
+ parser.add_argument('--pycharm-debug-host')
38
+ parser.add_argument('--pycharm-debug-version')
39
+
90
40
  parser.add_argument('--debug', action='store_true')
91
41
 
42
+ parser.add_argument('command', nargs='+')
43
+
92
44
  args = parser.parse_args()
93
45
 
94
46
  #
95
47
 
96
- payload_src = get_payload_src(file=args._payload_file) # noqa
48
+ bs = MainBootstrap(
49
+ main_config=MainConfig(
50
+ log_level='DEBUG' if args.debug else 'INFO',
51
+
52
+ debug=bool(args.debug),
53
+ ),
54
+
55
+ remote_config=RemoteConfig(
56
+ payload_file=args._payload_file, # noqa
57
+
58
+ pycharm_remote_debug=PycharmRemoteDebug(
59
+ port=args.pycharm_debug_port,
60
+ host=args.pycharm_debug_host,
61
+ install_version=args.pycharm_debug_version,
62
+ ) if args.pycharm_debug_port is not None else None,
63
+ ),
64
+ )
65
+
66
+ injector = main_bootstrap(
67
+ bs,
68
+ )
69
+
70
+ #
97
71
 
98
- remote_src = '\n\n'.join([
99
- '__name__ = "__remote__"',
100
- payload_src,
101
- '_remote_main()',
102
- ])
72
+ cmds = [
73
+ SubprocessCommand([c])
74
+ for c in args.command
75
+ ]
103
76
 
104
77
  #
105
78
 
106
- spawner = PySpawner(
107
- pyremote_build_bootstrap_cmd(__package__ or 'manage'),
79
+ tgt = RemoteSpawning.Target(
108
80
  shell=args.shell,
109
81
  shell_quote=args.shell_quote,
110
82
  python=args.python,
111
83
  )
112
84
 
113
- with spawner.spawn() as proc:
114
- res = PyremoteBootstrapDriver( # noqa
115
- remote_src,
116
- PyremoteBootstrapOptions(
117
- debug=args.debug,
118
- ),
119
- ).run(proc.stdout, proc.stdin)
120
-
121
- chan = Channel(proc.stdout, proc.stdin)
85
+ with injector[RemoteExecution].connect(tgt, bs) as rce:
86
+ for cmd in cmds:
87
+ r = rce.try_execute(cmd)
122
88
 
123
- #
89
+ print(injector[ObjMarshalerManager].marshal_obj(r, opts=ObjMarshalOptions(raw_bytes=True)))
124
90
 
125
- for ci in [
126
- SubprocessCommand(['python3', '-'], input=b'print(1)\n'),
127
- SubprocessCommand(['uname']),
128
- ]:
129
- chan.send_obj(ci, Command)
130
-
131
- o = chan.recv_obj(Command.Output)
91
+ #
132
92
 
133
- print(o)
93
+ print('Success')
134
94
 
135
95
 
136
96
  if __name__ == '__main__':
@@ -0,0 +1,12 @@
1
+ import dataclasses as dc
2
+ import typing as ta
3
+
4
+ from omlish.lite.marshal import ObjMarshalerManager
5
+
6
+
7
+ @dc.dataclass(frozen=True)
8
+ class ObjMarshalerInstaller:
9
+ fn: ta.Callable[[ObjMarshalerManager], None]
10
+
11
+
12
+ ObjMarshalerInstallers = ta.NewType('ObjMarshalerInstallers', ta.Sequence[ObjMarshalerInstaller])
File without changes
@@ -1,3 +1,4 @@
1
+ # ruff: noqa: UP006 UP007
1
2
  import json
2
3
  import struct
3
4
  import typing as ta
@@ -7,7 +8,10 @@ from omlish.lite.marshal import OBJ_MARSHALER_MANAGER
7
8
  from omlish.lite.marshal import ObjMarshalerManager
8
9
 
9
10
 
10
- class Channel:
11
+ T = ta.TypeVar('T')
12
+
13
+
14
+ class RemoteChannel:
11
15
  def __init__(
12
16
  self,
13
17
  input: ta.IO, # noqa
@@ -21,6 +25,9 @@ class Channel:
21
25
  self._output = output
22
26
  self._msh = msh
23
27
 
28
+ def set_marshaler(self, msh: ObjMarshalerManager) -> None:
29
+ self._msh = msh
30
+
24
31
  def send_obj(self, o: ta.Any, ty: ta.Any = None) -> None:
25
32
  j = json_dumps_compact(self._msh.marshal_obj(o, ty))
26
33
  d = j.encode('utf-8')
@@ -29,7 +36,7 @@ class Channel:
29
36
  self._output.write(d)
30
37
  self._output.flush()
31
38
 
32
- def recv_obj(self, ty: ta.Any) -> ta.Any:
39
+ def recv_obj(self, ty: ta.Type[T]) -> ta.Optional[T]:
33
40
  d = self._input.read(4)
34
41
  if not d:
35
42
  return None
@@ -0,0 +1,12 @@
1
+ # ruff: noqa: UP006 UP007
2
+ import dataclasses as dc
3
+ import typing as ta
4
+
5
+ from omlish.lite.pycharm import PycharmRemoteDebug
6
+
7
+
8
+ @dc.dataclass(frozen=True)
9
+ class RemoteConfig:
10
+ payload_file: ta.Optional[str] = None
11
+
12
+ pycharm_remote_debug: ta.Optional[PycharmRemoteDebug] = None