ominfra 0.0.0.dev141__tar.gz → 0.0.0.dev143__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
Files changed (110) hide show
  1. {ominfra-0.0.0.dev141/ominfra.egg-info → ominfra-0.0.0.dev143}/PKG-INFO +3 -3
  2. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra/manage/__init__.py +10 -0
  3. ominfra-0.0.0.dev143/ominfra/manage/__main__.py +4 -0
  4. ominfra-0.0.0.dev143/ominfra/manage/bootstrap.py +11 -0
  5. ominfra-0.0.0.dev143/ominfra/manage/bootstrap_.py +18 -0
  6. ominfra-0.0.0.dev143/ominfra/manage/commands/base.py +159 -0
  7. ominfra-0.0.0.dev143/ominfra/manage/commands/execution.py +23 -0
  8. ominfra-0.0.0.dev143/ominfra/manage/commands/inject.py +122 -0
  9. ominfra-0.0.0.dev143/ominfra/manage/commands/marshal.py +26 -0
  10. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra/manage/commands/subprocess.py +18 -17
  11. ominfra-0.0.0.dev143/ominfra/manage/config.py +10 -0
  12. ominfra-0.0.0.dev143/ominfra/manage/inject.py +55 -0
  13. ominfra-0.0.0.dev143/ominfra/manage/main.py +97 -0
  14. ominfra-0.0.0.dev143/ominfra/manage/marshal.py +12 -0
  15. ominfra-0.0.0.dev143/ominfra/manage/remote/channel.py +52 -0
  16. ominfra-0.0.0.dev143/ominfra/manage/remote/config.py +12 -0
  17. ominfra-0.0.0.dev143/ominfra/manage/remote/execution.py +193 -0
  18. ominfra-0.0.0.dev143/ominfra/manage/remote/inject.py +29 -0
  19. {ominfra-0.0.0.dev141/ominfra/manage → ominfra-0.0.0.dev143/ominfra/manage/remote}/payload.py +7 -1
  20. {ominfra-0.0.0.dev141/ominfra/manage → ominfra-0.0.0.dev143/ominfra/manage/remote}/spawning.py +31 -35
  21. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra/pyremote.py +53 -14
  22. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra/scripts/journald2aws.py +216 -131
  23. ominfra-0.0.0.dev143/ominfra/scripts/manage.py +3588 -0
  24. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra/scripts/supervisor.py +203 -130
  25. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra/supervisor/inject.py +2 -1
  26. ominfra-0.0.0.dev143/ominfra/tools/__init__.py +0 -0
  27. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143/ominfra.egg-info}/PKG-INFO +3 -3
  28. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra.egg-info/SOURCES.txt +16 -2
  29. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra.egg-info/requires.txt +2 -2
  30. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/pyproject.toml +3 -3
  31. ominfra-0.0.0.dev141/ominfra/manage/commands/base.py +0 -27
  32. ominfra-0.0.0.dev141/ominfra/manage/main.py +0 -175
  33. ominfra-0.0.0.dev141/ominfra/scripts/manage.py +0 -1860
  34. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/LICENSE +0 -0
  35. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/MANIFEST.in +0 -0
  36. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/README.rst +0 -0
  37. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra/.manifests.json +0 -0
  38. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra/__about__.py +0 -0
  39. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra/__init__.py +0 -0
  40. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra/clouds/__init__.py +0 -0
  41. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra/clouds/aws/__init__.py +0 -0
  42. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra/clouds/aws/__main__.py +0 -0
  43. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra/clouds/aws/auth.py +0 -0
  44. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra/clouds/aws/cli.py +0 -0
  45. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra/clouds/aws/dataclasses.py +0 -0
  46. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra/clouds/aws/journald2aws/__init__.py +0 -0
  47. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra/clouds/aws/journald2aws/__main__.py +0 -0
  48. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra/clouds/aws/journald2aws/cursor.py +0 -0
  49. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra/clouds/aws/journald2aws/driver.py +0 -0
  50. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra/clouds/aws/journald2aws/main.py +0 -0
  51. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra/clouds/aws/journald2aws/poster.py +0 -0
  52. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra/clouds/aws/logs.py +0 -0
  53. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra/clouds/aws/metadata.py +0 -0
  54. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra/clouds/gcp/__init__.py +0 -0
  55. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra/clouds/gcp/auth.py +0 -0
  56. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra/cmds.py +0 -0
  57. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra/configs.py +0 -0
  58. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra/journald/__init__.py +0 -0
  59. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra/journald/fields.py +0 -0
  60. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra/journald/genmessages.py +0 -0
  61. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra/journald/messages.py +0 -0
  62. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra/journald/tailer.py +0 -0
  63. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra/manage/commands/__init__.py +0 -0
  64. {ominfra-0.0.0.dev141/ominfra/scripts → ominfra-0.0.0.dev143/ominfra/manage/remote}/__init__.py +0 -0
  65. {ominfra-0.0.0.dev141/ominfra/supervisor/utils → ominfra-0.0.0.dev143/ominfra/scripts}/__init__.py +0 -0
  66. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra/ssh.py +0 -0
  67. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra/supervisor/LICENSE.txt +0 -0
  68. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra/supervisor/__init__.py +0 -0
  69. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra/supervisor/__main__.py +0 -0
  70. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra/supervisor/configs.py +0 -0
  71. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra/supervisor/dispatchers.py +0 -0
  72. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra/supervisor/dispatchersimpl.py +0 -0
  73. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra/supervisor/events.py +0 -0
  74. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra/supervisor/exceptions.py +0 -0
  75. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra/supervisor/groups.py +0 -0
  76. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra/supervisor/groupsimpl.py +0 -0
  77. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra/supervisor/http.py +0 -0
  78. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra/supervisor/io.py +0 -0
  79. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra/supervisor/main.py +0 -0
  80. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra/supervisor/pipes.py +0 -0
  81. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra/supervisor/privileges.py +0 -0
  82. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra/supervisor/process.py +0 -0
  83. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra/supervisor/processimpl.py +0 -0
  84. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra/supervisor/setup.py +0 -0
  85. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra/supervisor/setupimpl.py +0 -0
  86. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra/supervisor/signals.py +0 -0
  87. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra/supervisor/spawning.py +0 -0
  88. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra/supervisor/spawningimpl.py +0 -0
  89. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra/supervisor/states.py +0 -0
  90. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra/supervisor/supervisor.py +0 -0
  91. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra/supervisor/types.py +0 -0
  92. {ominfra-0.0.0.dev141/ominfra/tailscale → ominfra-0.0.0.dev143/ominfra/supervisor/utils}/__init__.py +0 -0
  93. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra/supervisor/utils/collections.py +0 -0
  94. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra/supervisor/utils/diag.py +0 -0
  95. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra/supervisor/utils/fds.py +0 -0
  96. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra/supervisor/utils/fs.py +0 -0
  97. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra/supervisor/utils/os.py +0 -0
  98. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra/supervisor/utils/ostypes.py +0 -0
  99. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra/supervisor/utils/signals.py +0 -0
  100. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra/supervisor/utils/strings.py +0 -0
  101. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra/supervisor/utils/users.py +0 -0
  102. {ominfra-0.0.0.dev141/ominfra/tools → ominfra-0.0.0.dev143/ominfra/tailscale}/__init__.py +0 -0
  103. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra/tailscale/api.py +0 -0
  104. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra/tailscale/cli.py +0 -0
  105. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra/threadworkers.py +0 -0
  106. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra/tools/listresources.py +0 -0
  107. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra.egg-info/dependency_links.txt +0 -0
  108. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra.egg-info/entry_points.txt +0 -0
  109. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/ominfra.egg-info/top_level.txt +0 -0
  110. {ominfra-0.0.0.dev141 → ominfra-0.0.0.dev143}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ominfra
3
- Version: 0.0.0.dev141
3
+ Version: 0.0.0.dev143
4
4
  Summary: ominfra
5
5
  Author: wrmsr
6
6
  License: BSD-3-Clause
@@ -12,8 +12,8 @@ Classifier: Operating System :: OS Independent
12
12
  Classifier: Operating System :: POSIX
13
13
  Requires-Python: >=3.12
14
14
  License-File: LICENSE
15
- Requires-Dist: omdev==0.0.0.dev141
16
- Requires-Dist: omlish==0.0.0.dev141
15
+ Requires-Dist: omdev==0.0.0.dev143
16
+ Requires-Dist: omlish==0.0.0.dev143
17
17
  Provides-Extra: all
18
18
  Requires-Dist: paramiko~=3.5; extra == "all"
19
19
  Requires-Dist: asyncssh~=2.18; extra == "all"
@@ -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
@@ -0,0 +1,159 @@
1
+ # ruff: noqa: UP006 UP007
2
+ import abc
3
+ import dataclasses as dc
4
+ import logging
5
+ import traceback
6
+ import typing as ta
7
+
8
+ from omlish.lite.check import check_isinstance
9
+ from omlish.lite.strings import snake_case
10
+
11
+
12
+ CommandT = ta.TypeVar('CommandT', bound='Command')
13
+ CommandOutputT = ta.TypeVar('CommandOutputT', bound='Command.Output')
14
+
15
+
16
+ ##
17
+
18
+
19
+ @dc.dataclass(frozen=True)
20
+ class Command(abc.ABC, ta.Generic[CommandOutputT]):
21
+ @dc.dataclass(frozen=True)
22
+ class Output(abc.ABC): # noqa
23
+ pass
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
+
29
+
30
+ ##
31
+
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
+
86
+ class CommandExecutor(abc.ABC, ta.Generic[CommandT, CommandOutputT]):
87
+ @abc.abstractmethod
88
+ def execute(self, cmd: CommandT) -> CommandOutputT:
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
+ )
@@ -5,6 +5,8 @@ import subprocess
5
5
  import time
6
6
  import typing as ta
7
7
 
8
+ from omlish.lite.subprocesses import SUBPROCESS_CHANNEL_OPTION_VALUES
9
+ from omlish.lite.subprocesses import SubprocessChannelOption
8
10
  from omlish.lite.subprocesses import subprocess_maybe_shell_wrap_exec
9
11
 
10
12
  from .base import Command
@@ -16,21 +18,21 @@ from .base import CommandExecutor
16
18
 
17
19
  @dc.dataclass(frozen=True)
18
20
  class SubprocessCommand(Command['SubprocessCommand.Output']):
19
- args: ta.Sequence[str]
21
+ cmd: ta.Sequence[str]
20
22
 
21
23
  shell: bool = False
22
24
  cwd: ta.Optional[str] = None
23
25
  env: ta.Optional[ta.Mapping[str, str]] = None
24
26
 
25
- capture_stdout: bool = False
26
- capture_stderr: bool = False
27
+ stdout: str = 'pipe' # SubprocessChannelOption
28
+ stderr: str = 'pipe' # SubprocessChannelOption
27
29
 
28
30
  input: ta.Optional[bytes] = None
29
31
  timeout: ta.Optional[float] = None
30
32
 
31
33
  def __post_init__(self) -> None:
32
- if isinstance(self.args, str):
33
- raise TypeError(self.args)
34
+ if isinstance(self.cmd, str):
35
+ raise TypeError(self.cmd)
34
36
 
35
37
  @dc.dataclass(frozen=True)
36
38
  class Output(Command.Output):
@@ -48,24 +50,23 @@ class SubprocessCommand(Command['SubprocessCommand.Output']):
48
50
 
49
51
  class SubprocessCommandExecutor(CommandExecutor[SubprocessCommand, SubprocessCommand.Output]):
50
52
  def execute(self, inp: SubprocessCommand) -> SubprocessCommand.Output:
51
- proc = subprocess.Popen(
52
- subprocess_maybe_shell_wrap_exec(*inp.args),
53
+ with subprocess.Popen(
54
+ subprocess_maybe_shell_wrap_exec(*inp.cmd),
53
55
 
54
56
  shell=inp.shell,
55
57
  cwd=inp.cwd,
56
58
  env={**os.environ, **(inp.env or {})},
57
59
 
58
60
  stdin=subprocess.PIPE if inp.input is not None else None,
59
- stdout=subprocess.PIPE if inp.capture_stdout else None,
60
- stderr=subprocess.PIPE if inp.capture_stderr else None,
61
- )
62
-
63
- start_time = time.time()
64
- stdout, stderr = proc.communicate(
65
- input=inp.input,
66
- timeout=inp.timeout,
67
- )
68
- end_time = time.time()
61
+ stdout=SUBPROCESS_CHANNEL_OPTION_VALUES[ta.cast(SubprocessChannelOption, inp.stdout)],
62
+ stderr=SUBPROCESS_CHANNEL_OPTION_VALUES[ta.cast(SubprocessChannelOption, inp.stderr)],
63
+ ) as proc:
64
+ start_time = time.time()
65
+ stdout, stderr = proc.communicate(
66
+ input=inp.input,
67
+ timeout=inp.timeout,
68
+ )
69
+ end_time = time.time()
69
70
 
70
71
  return SubprocessCommand.Output(
71
72
  rc=proc.returncode,
@@ -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)
@@ -0,0 +1,97 @@
1
+ #!/usr/bin/env python3
2
+ # @omlish-amalg ../scripts/manage.py
3
+ # ruff: noqa: UP006 UP007
4
+ """
5
+ manage.py -s 'docker run -i python:3.12'
6
+ manage.py -s 'ssh -i /foo/bar.pem foo@bar.baz' -q --python=python3.8
7
+ """
8
+ from omlish.lite.logs import log # noqa
9
+ from omlish.lite.marshal import ObjMarshalOptions
10
+ from omlish.lite.marshal import ObjMarshalerManager
11
+ from omlish.lite.pycharm import PycharmRemoteDebug
12
+
13
+ from .bootstrap import MainBootstrap
14
+ from .bootstrap_ import main_bootstrap
15
+ from .commands.subprocess import SubprocessCommand
16
+ from .config import MainConfig
17
+ from .remote.config import RemoteConfig
18
+ from .remote.execution import RemoteExecution
19
+ from .remote.spawning import RemoteSpawning
20
+
21
+
22
+ ##
23
+
24
+
25
+ def _main() -> None:
26
+ import argparse
27
+
28
+ parser = argparse.ArgumentParser()
29
+
30
+ parser.add_argument('--_payload-file')
31
+
32
+ parser.add_argument('-s', '--shell')
33
+ parser.add_argument('-q', '--shell-quote', action='store_true')
34
+ parser.add_argument('--python', default='python3')
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
+
40
+ parser.add_argument('--debug', action='store_true')
41
+
42
+ parser.add_argument('command', nargs='+')
43
+
44
+ args = parser.parse_args()
45
+
46
+ #
47
+
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
+ #
71
+
72
+ cmds = [
73
+ SubprocessCommand([c])
74
+ for c in args.command
75
+ ]
76
+
77
+ #
78
+
79
+ tgt = RemoteSpawning.Target(
80
+ shell=args.shell,
81
+ shell_quote=args.shell_quote,
82
+ python=args.python,
83
+ )
84
+
85
+ with injector[RemoteExecution].connect(tgt, bs) as rce:
86
+ for cmd in cmds:
87
+ r = rce.try_execute(cmd)
88
+
89
+ print(injector[ObjMarshalerManager].marshal_obj(r, opts=ObjMarshalOptions(raw_bytes=True)))
90
+
91
+ #
92
+
93
+ print('Success')
94
+
95
+
96
+ if __name__ == '__main__':
97
+ _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])