ominfra 0.0.0.dev152__py3-none-any.whl → 0.0.0.dev154__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/bootstrap.py +3 -0
- ominfra/manage/bootstrap_.py +1 -0
- ominfra/manage/commands/interp.py +0 -3
- ominfra/manage/commands/subprocess.py +0 -3
- ominfra/manage/deploy/{command.py → commands.py} +0 -3
- ominfra/manage/deploy/inject.py +2 -2
- ominfra/manage/inject.py +8 -1
- ominfra/manage/main.py +2 -0
- ominfra/manage/remote/_main.py +2 -2
- ominfra/manage/remote/config.py +2 -0
- ominfra/manage/remote/connection.py +48 -0
- ominfra/manage/remote/execution.py +90 -12
- ominfra/manage/remote/inject.py +19 -4
- ominfra/manage/system/__init__.py +0 -0
- ominfra/manage/system/commands.py +24 -0
- ominfra/manage/system/config.py +8 -0
- ominfra/manage/system/inject.py +54 -0
- ominfra/manage/system/packages.py +106 -0
- ominfra/manage/system/types.py +5 -0
- ominfra/scripts/manage.py +400 -38
- {ominfra-0.0.0.dev152.dist-info → ominfra-0.0.0.dev154.dist-info}/METADATA +3 -3
- {ominfra-0.0.0.dev152.dist-info → ominfra-0.0.0.dev154.dist-info}/RECORD +26 -20
- {ominfra-0.0.0.dev152.dist-info → ominfra-0.0.0.dev154.dist-info}/LICENSE +0 -0
- {ominfra-0.0.0.dev152.dist-info → ominfra-0.0.0.dev154.dist-info}/WHEEL +0 -0
- {ominfra-0.0.0.dev152.dist-info → ominfra-0.0.0.dev154.dist-info}/entry_points.txt +0 -0
- {ominfra-0.0.0.dev152.dist-info → ominfra-0.0.0.dev154.dist-info}/top_level.txt +0 -0
ominfra/manage/bootstrap.py
CHANGED
@@ -2,6 +2,7 @@ import dataclasses as dc
|
|
2
2
|
|
3
3
|
from .config import MainConfig
|
4
4
|
from .remote.config import RemoteConfig
|
5
|
+
from .system.config import SystemConfig
|
5
6
|
|
6
7
|
|
7
8
|
@dc.dataclass(frozen=True)
|
@@ -9,3 +10,5 @@ class MainBootstrap:
|
|
9
10
|
main_config: MainConfig = MainConfig()
|
10
11
|
|
11
12
|
remote_config: RemoteConfig = RemoteConfig()
|
13
|
+
|
14
|
+
system_config: SystemConfig = SystemConfig()
|
ominfra/manage/bootstrap_.py
CHANGED
@@ -25,9 +25,6 @@ class InterpCommand(Command['InterpCommand.Output']):
|
|
25
25
|
opts: InterpOpts
|
26
26
|
|
27
27
|
|
28
|
-
##
|
29
|
-
|
30
|
-
|
31
28
|
class InterpCommandExecutor(CommandExecutor[InterpCommand, InterpCommand.Output]):
|
32
29
|
async def execute(self, cmd: InterpCommand) -> InterpCommand.Output:
|
33
30
|
i = InterpSpecifier.parse(check.not_none(cmd.spec))
|
@@ -48,9 +48,6 @@ class SubprocessCommand(Command['SubprocessCommand.Output']):
|
|
48
48
|
stderr: ta.Optional[bytes] = None
|
49
49
|
|
50
50
|
|
51
|
-
##
|
52
|
-
|
53
|
-
|
54
51
|
class SubprocessCommandExecutor(CommandExecutor[SubprocessCommand, SubprocessCommand.Output]):
|
55
52
|
async def execute(self, cmd: SubprocessCommand) -> SubprocessCommand.Output:
|
56
53
|
proc: asyncio.subprocess.Process
|
@@ -17,9 +17,6 @@ class DeployCommand(Command['DeployCommand.Output']):
|
|
17
17
|
pass
|
18
18
|
|
19
19
|
|
20
|
-
##
|
21
|
-
|
22
|
-
|
23
20
|
class DeployCommandExecutor(CommandExecutor[DeployCommand, DeployCommand.Output]):
|
24
21
|
async def execute(self, cmd: DeployCommand) -> DeployCommand.Output:
|
25
22
|
log.info('Deploying!')
|
ominfra/manage/deploy/inject.py
CHANGED
@@ -6,8 +6,8 @@ from omlish.lite.inject import InjectorBindings
|
|
6
6
|
from omlish.lite.inject import inj
|
7
7
|
|
8
8
|
from ..commands.inject import bind_command
|
9
|
-
from .
|
10
|
-
from .
|
9
|
+
from .commands import DeployCommand
|
10
|
+
from .commands import DeployCommandExecutor
|
11
11
|
|
12
12
|
|
13
13
|
def bind_deploy(
|
ominfra/manage/inject.py
CHANGED
@@ -13,6 +13,8 @@ from .marshal import ObjMarshalerInstaller
|
|
13
13
|
from .marshal import ObjMarshalerInstallers
|
14
14
|
from .remote.config import RemoteConfig
|
15
15
|
from .remote.inject import bind_remote
|
16
|
+
from .system.config import SystemConfig
|
17
|
+
from .system.inject import bind_system
|
16
18
|
|
17
19
|
|
18
20
|
##
|
@@ -22,6 +24,7 @@ def bind_main(
|
|
22
24
|
*,
|
23
25
|
main_config: MainConfig,
|
24
26
|
remote_config: RemoteConfig,
|
27
|
+
system_config: SystemConfig,
|
25
28
|
) -> InjectorBindings:
|
26
29
|
lst: ta.List[InjectorBindingOrBindings] = [
|
27
30
|
inj.bind(main_config),
|
@@ -30,11 +33,15 @@ def bind_main(
|
|
30
33
|
main_config=main_config,
|
31
34
|
),
|
32
35
|
|
36
|
+
bind_deploy(),
|
37
|
+
|
33
38
|
bind_remote(
|
34
39
|
remote_config=remote_config,
|
35
40
|
),
|
36
41
|
|
37
|
-
|
42
|
+
bind_system(
|
43
|
+
system_config=system_config,
|
44
|
+
),
|
38
45
|
]
|
39
46
|
|
40
47
|
#
|
ominfra/manage/main.py
CHANGED
ominfra/manage/remote/_main.py
CHANGED
@@ -8,8 +8,8 @@ import threading
|
|
8
8
|
import time
|
9
9
|
import typing as ta
|
10
10
|
|
11
|
-
from omlish.
|
12
|
-
from omlish.
|
11
|
+
from omlish.asyncs.asyncio.streams import asyncio_open_stream_reader
|
12
|
+
from omlish.asyncs.asyncio.streams import asyncio_open_stream_writer
|
13
13
|
from omlish.lite.cached import cached_nullary
|
14
14
|
from omlish.lite.check import check
|
15
15
|
from omlish.lite.inject import Injector
|
ominfra/manage/remote/config.py
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
# ruff: noqa: UP006 UP007
|
2
2
|
import abc
|
3
|
+
import asyncio
|
3
4
|
import contextlib
|
4
5
|
import typing as ta
|
5
6
|
|
7
|
+
from omlish.asyncs.asyncio.channels import asyncio_create_bytes_channel
|
6
8
|
from omlish.lite.cached import cached_nullary
|
7
9
|
from omlish.lite.marshal import ObjMarshalerManager
|
8
10
|
|
@@ -10,9 +12,11 @@ from ...pyremote import PyremoteBootstrapDriver
|
|
10
12
|
from ...pyremote import PyremoteBootstrapOptions
|
11
13
|
from ...pyremote import pyremote_build_bootstrap_cmd
|
12
14
|
from ..bootstrap import MainBootstrap
|
15
|
+
from ..commands.execution import LocalCommandExecutor
|
13
16
|
from ._main import _remote_execution_main # noqa
|
14
17
|
from .channel import RemoteChannelImpl
|
15
18
|
from .execution import RemoteCommandExecutor
|
19
|
+
from .execution import _RemoteCommandHandler
|
16
20
|
from .payload import RemoteExecutionPayloadFile
|
17
21
|
from .payload import get_remote_payload_src
|
18
22
|
from .spawning import RemoteSpawning
|
@@ -104,3 +108,47 @@ class PyremoteRemoteExecutionConnector(RemoteExecutionConnector):
|
|
104
108
|
await rce.start()
|
105
109
|
|
106
110
|
yield rce
|
111
|
+
|
112
|
+
|
113
|
+
##
|
114
|
+
|
115
|
+
|
116
|
+
class InProcessRemoteExecutionConnector(RemoteExecutionConnector):
|
117
|
+
def __init__(
|
118
|
+
self,
|
119
|
+
*,
|
120
|
+
msh: ObjMarshalerManager,
|
121
|
+
local_executor: LocalCommandExecutor,
|
122
|
+
) -> None:
|
123
|
+
super().__init__()
|
124
|
+
|
125
|
+
self._msh = msh
|
126
|
+
self._local_executor = local_executor
|
127
|
+
|
128
|
+
@contextlib.asynccontextmanager
|
129
|
+
async def connect(
|
130
|
+
self,
|
131
|
+
tgt: RemoteSpawning.Target,
|
132
|
+
bs: MainBootstrap,
|
133
|
+
) -> ta.AsyncGenerator[RemoteCommandExecutor, None]:
|
134
|
+
r0, w0 = asyncio_create_bytes_channel()
|
135
|
+
r1, w1 = asyncio_create_bytes_channel()
|
136
|
+
|
137
|
+
remote_chan = RemoteChannelImpl(r0, w1, msh=self._msh)
|
138
|
+
local_chan = RemoteChannelImpl(r1, w0, msh=self._msh)
|
139
|
+
|
140
|
+
rch = _RemoteCommandHandler(
|
141
|
+
remote_chan,
|
142
|
+
self._local_executor,
|
143
|
+
)
|
144
|
+
rch_task = asyncio.create_task(rch.run()) # noqa
|
145
|
+
try:
|
146
|
+
rce: RemoteCommandExecutor
|
147
|
+
async with contextlib.aclosing(RemoteCommandExecutor(local_chan)) as rce:
|
148
|
+
await rce.start()
|
149
|
+
|
150
|
+
yield rce
|
151
|
+
|
152
|
+
finally:
|
153
|
+
rch.stop()
|
154
|
+
await rch_task
|
@@ -1,9 +1,14 @@
|
|
1
1
|
# ruff: noqa: UP006 UP007
|
2
|
+
"""
|
3
|
+
TODO:
|
4
|
+
- sequence all messages
|
5
|
+
"""
|
2
6
|
import abc
|
3
7
|
import asyncio
|
4
8
|
import dataclasses as dc
|
5
9
|
import itertools
|
6
10
|
import logging
|
11
|
+
import time
|
7
12
|
import typing as ta
|
8
13
|
|
9
14
|
from omlish.lite.check import check
|
@@ -96,38 +101,80 @@ class _RemoteLogHandler(logging.Handler):
|
|
96
101
|
|
97
102
|
|
98
103
|
class _RemoteCommandHandler:
|
104
|
+
DEFAULT_PING_INTERVAL_S: float = 3.
|
105
|
+
|
99
106
|
def __init__(
|
100
107
|
self,
|
101
108
|
chan: RemoteChannel,
|
102
109
|
executor: CommandExecutor,
|
103
110
|
*,
|
104
111
|
stop: ta.Optional[asyncio.Event] = None,
|
112
|
+
ping_interval_s: float = DEFAULT_PING_INTERVAL_S,
|
105
113
|
) -> None:
|
106
114
|
super().__init__()
|
107
115
|
|
108
116
|
self._chan = chan
|
109
117
|
self._executor = executor
|
110
118
|
self._stop = stop if stop is not None else asyncio.Event()
|
119
|
+
self._ping_interval_s = ping_interval_s
|
111
120
|
|
112
121
|
self._cmds_by_seq: ta.Dict[int, _RemoteCommandHandler._Command] = {}
|
113
122
|
|
123
|
+
self._last_ping_send: float = 0.
|
124
|
+
self._ping_in_flight: bool = False
|
125
|
+
self._last_ping_recv: ta.Optional[float] = None
|
126
|
+
|
127
|
+
def stop(self) -> None:
|
128
|
+
self._stop.set()
|
129
|
+
|
114
130
|
@dc.dataclass(frozen=True)
|
115
131
|
class _Command:
|
116
132
|
req: _RemoteProtocol.CommandRequest
|
117
133
|
fut: asyncio.Future
|
118
134
|
|
119
135
|
async def run(self) -> None:
|
136
|
+
log.debug('_RemoteCommandHandler loop start: %r', self)
|
137
|
+
|
120
138
|
stop_task = asyncio.create_task(self._stop.wait())
|
121
139
|
recv_task: ta.Optional[asyncio.Task] = None
|
122
140
|
|
123
141
|
while not self._stop.is_set():
|
124
142
|
if recv_task is None:
|
125
|
-
recv_task = asyncio.create_task(_RemoteProtocol.
|
143
|
+
recv_task = asyncio.create_task(_RemoteProtocol.Message.recv(self._chan))
|
126
144
|
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
145
|
+
if not self._ping_in_flight:
|
146
|
+
if not self._last_ping_recv:
|
147
|
+
ping_wait_time = 0.
|
148
|
+
else:
|
149
|
+
ping_wait_time = self._ping_interval_s - (time.time() - self._last_ping_recv)
|
150
|
+
else:
|
151
|
+
ping_wait_time = float('inf')
|
152
|
+
wait_time = min(self._ping_interval_s, ping_wait_time)
|
153
|
+
log.debug('_RemoteCommandHandler loop wait: %f', wait_time)
|
154
|
+
|
155
|
+
done, pending = await asyncio.wait(
|
156
|
+
[
|
157
|
+
stop_task,
|
158
|
+
recv_task,
|
159
|
+
],
|
160
|
+
return_when=asyncio.FIRST_COMPLETED,
|
161
|
+
timeout=wait_time,
|
162
|
+
)
|
163
|
+
|
164
|
+
#
|
165
|
+
|
166
|
+
if (
|
167
|
+
(time.time() - self._last_ping_send >= self._ping_interval_s) and
|
168
|
+
not self._ping_in_flight
|
169
|
+
):
|
170
|
+
now = time.time()
|
171
|
+
self._last_ping_send = now
|
172
|
+
self._ping_in_flight = True
|
173
|
+
await _RemoteProtocol.PingRequest(
|
174
|
+
time=now,
|
175
|
+
).send(self._chan)
|
176
|
+
|
177
|
+
#
|
131
178
|
|
132
179
|
if recv_task in done:
|
133
180
|
msg: ta.Optional[_RemoteProtocol.Message] = check.isinstance(
|
@@ -141,6 +188,20 @@ class _RemoteCommandHandler:
|
|
141
188
|
|
142
189
|
await self._handle_message(msg)
|
143
190
|
|
191
|
+
log.debug('_RemoteCommandHandler loop stopping: %r', self)
|
192
|
+
|
193
|
+
for task in [
|
194
|
+
stop_task,
|
195
|
+
recv_task,
|
196
|
+
]:
|
197
|
+
if task is not None and not task.done():
|
198
|
+
task.cancel()
|
199
|
+
|
200
|
+
for cmd in self._cmds_by_seq.values():
|
201
|
+
cmd.fut.cancel()
|
202
|
+
|
203
|
+
log.debug('_RemoteCommandHandler loop exited: %r', self)
|
204
|
+
|
144
205
|
async def _handle_message(self, msg: _RemoteProtocol.Message) -> None:
|
145
206
|
if isinstance(msg, _RemoteProtocol.PingRequest):
|
146
207
|
log.debug('Ping: %r', msg)
|
@@ -148,6 +209,12 @@ class _RemoteCommandHandler:
|
|
148
209
|
time=msg.time,
|
149
210
|
).send(self._chan)
|
150
211
|
|
212
|
+
elif isinstance(msg, _RemoteProtocol.PingResponse):
|
213
|
+
latency_s = time.time() - msg.time
|
214
|
+
log.debug('Pong: %0.2f ms %r', latency_s * 1000., msg)
|
215
|
+
self._last_ping_recv = time.time()
|
216
|
+
self._ping_in_flight = False
|
217
|
+
|
151
218
|
elif isinstance(msg, _RemoteProtocol.CommandRequest):
|
152
219
|
fut = asyncio.create_task(self._handle_command_request(msg))
|
153
220
|
self._cmds_by_seq[msg.seq] = _RemoteCommandHandler._Command(
|
@@ -229,16 +296,23 @@ class RemoteCommandExecutor(CommandExecutor):
|
|
229
296
|
if recv_task is None:
|
230
297
|
recv_task = asyncio.create_task(_RemoteProtocol.Message.recv(self._chan))
|
231
298
|
|
232
|
-
done, pending = await asyncio.wait(
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
299
|
+
done, pending = await asyncio.wait(
|
300
|
+
[
|
301
|
+
stop_task,
|
302
|
+
queue_task,
|
303
|
+
recv_task,
|
304
|
+
],
|
305
|
+
return_when=asyncio.FIRST_COMPLETED,
|
306
|
+
)
|
307
|
+
|
308
|
+
#
|
237
309
|
|
238
310
|
if queue_task in done:
|
239
311
|
req = check.isinstance(queue_task.result(), RemoteCommandExecutor._Request)
|
240
312
|
queue_task = None
|
241
|
-
await self.
|
313
|
+
await self._handle_queued_request(req)
|
314
|
+
|
315
|
+
#
|
242
316
|
|
243
317
|
if recv_task in done:
|
244
318
|
msg: ta.Optional[_RemoteProtocol.Message] = check.isinstance(
|
@@ -268,7 +342,7 @@ class RemoteCommandExecutor(CommandExecutor):
|
|
268
342
|
|
269
343
|
log.debug('RemoteCommandExecutor loop exited: %r', self)
|
270
344
|
|
271
|
-
async def
|
345
|
+
async def _handle_queued_request(self, req: _Request) -> None:
|
272
346
|
self._reqs_by_seq[req.seq] = req
|
273
347
|
await _RemoteProtocol.CommandRequest(
|
274
348
|
seq=req.seq,
|
@@ -282,6 +356,10 @@ class RemoteCommandExecutor(CommandExecutor):
|
|
282
356
|
time=msg.time,
|
283
357
|
).send(self._chan)
|
284
358
|
|
359
|
+
elif isinstance(msg, _RemoteProtocol.PingResponse):
|
360
|
+
latency_s = time.time() - msg.time
|
361
|
+
log.debug('Pong: %0.2f ms %r', latency_s * 1000., msg)
|
362
|
+
|
285
363
|
elif isinstance(msg, _RemoteProtocol.LogResponse):
|
286
364
|
log.info(msg.s)
|
287
365
|
|
ominfra/manage/remote/inject.py
CHANGED
@@ -6,6 +6,7 @@ from omlish.lite.inject import InjectorBindings
|
|
6
6
|
from omlish.lite.inject import inj
|
7
7
|
|
8
8
|
from .config import RemoteConfig
|
9
|
+
from .connection import InProcessRemoteExecutionConnector
|
9
10
|
from .connection import PyremoteRemoteExecutionConnector
|
10
11
|
from .connection import RemoteExecutionConnector
|
11
12
|
from .payload import RemoteExecutionPayloadFile
|
@@ -22,12 +23,26 @@ def bind_remote(
|
|
22
23
|
|
23
24
|
inj.bind(SubprocessRemoteSpawning, singleton=True),
|
24
25
|
inj.bind(RemoteSpawning, to_key=SubprocessRemoteSpawning),
|
25
|
-
|
26
|
-
inj.bind(PyremoteRemoteExecutionConnector, singleton=True),
|
27
|
-
inj.bind(RemoteExecutionConnector, to_key=PyremoteRemoteExecutionConnector),
|
28
26
|
]
|
29
27
|
|
28
|
+
#
|
29
|
+
|
30
|
+
if remote_config.use_in_process_remote_executor:
|
31
|
+
lst.extend([
|
32
|
+
inj.bind(InProcessRemoteExecutionConnector, singleton=True),
|
33
|
+
inj.bind(RemoteExecutionConnector, to_key=InProcessRemoteExecutionConnector),
|
34
|
+
])
|
35
|
+
else:
|
36
|
+
lst.extend([
|
37
|
+
inj.bind(PyremoteRemoteExecutionConnector, singleton=True),
|
38
|
+
inj.bind(RemoteExecutionConnector, to_key=PyremoteRemoteExecutionConnector),
|
39
|
+
])
|
40
|
+
|
41
|
+
#
|
42
|
+
|
30
43
|
if (pf := remote_config.payload_file) is not None:
|
31
|
-
lst.append(inj.bind(pf,
|
44
|
+
lst.append(inj.bind(pf, key=RemoteExecutionPayloadFile))
|
45
|
+
|
46
|
+
#
|
32
47
|
|
33
48
|
return inj.as_bindings(*lst)
|
File without changes
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
import dataclasses as dc
|
3
|
+
|
4
|
+
from omlish.lite.logs import log
|
5
|
+
|
6
|
+
from ..commands.base import Command
|
7
|
+
from ..commands.base import CommandExecutor
|
8
|
+
|
9
|
+
|
10
|
+
##
|
11
|
+
|
12
|
+
|
13
|
+
@dc.dataclass(frozen=True)
|
14
|
+
class CheckSystemPackageCommand(Command['CheckSystemPackageCommand.Output']):
|
15
|
+
@dc.dataclass(frozen=True)
|
16
|
+
class Output(Command.Output):
|
17
|
+
pass
|
18
|
+
|
19
|
+
|
20
|
+
class CheckSystemPackageCommandExecutor(CommandExecutor[CheckSystemPackageCommand, CheckSystemPackageCommand.Output]):
|
21
|
+
async def execute(self, cmd: CheckSystemPackageCommand) -> CheckSystemPackageCommand.Output:
|
22
|
+
log.info('Checking system package!')
|
23
|
+
|
24
|
+
return CheckSystemPackageCommand.Output()
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
import sys
|
3
|
+
import typing as ta
|
4
|
+
|
5
|
+
from omlish.lite.inject import InjectorBindingOrBindings
|
6
|
+
from omlish.lite.inject import InjectorBindings
|
7
|
+
from omlish.lite.inject import inj
|
8
|
+
|
9
|
+
from ..commands.inject import bind_command
|
10
|
+
from .commands import CheckSystemPackageCommand
|
11
|
+
from .commands import CheckSystemPackageCommandExecutor
|
12
|
+
from .config import SystemConfig
|
13
|
+
from .packages import AptSystemPackageManager
|
14
|
+
from .packages import BrewSystemPackageManager
|
15
|
+
from .packages import SystemPackageManager
|
16
|
+
from .types import SystemPlatform
|
17
|
+
|
18
|
+
|
19
|
+
def bind_system(
|
20
|
+
*,
|
21
|
+
system_config: SystemConfig,
|
22
|
+
) -> InjectorBindings:
|
23
|
+
lst: ta.List[InjectorBindingOrBindings] = [
|
24
|
+
inj.bind(system_config),
|
25
|
+
]
|
26
|
+
|
27
|
+
#
|
28
|
+
|
29
|
+
platform = system_config.platform or sys.platform
|
30
|
+
lst.append(inj.bind(platform, key=SystemPlatform))
|
31
|
+
|
32
|
+
#
|
33
|
+
|
34
|
+
if platform == 'linux':
|
35
|
+
lst.extend([
|
36
|
+
inj.bind(AptSystemPackageManager, singleton=True),
|
37
|
+
inj.bind(SystemPackageManager, to_key=AptSystemPackageManager),
|
38
|
+
])
|
39
|
+
|
40
|
+
elif platform == 'darwin':
|
41
|
+
lst.extend([
|
42
|
+
inj.bind(BrewSystemPackageManager, singleton=True),
|
43
|
+
inj.bind(SystemPackageManager, to_key=BrewSystemPackageManager),
|
44
|
+
])
|
45
|
+
|
46
|
+
#
|
47
|
+
|
48
|
+
lst.extend([
|
49
|
+
bind_command(CheckSystemPackageCommand, CheckSystemPackageCommandExecutor),
|
50
|
+
])
|
51
|
+
|
52
|
+
#
|
53
|
+
|
54
|
+
return inj.as_bindings(*lst)
|
@@ -0,0 +1,106 @@
|
|
1
|
+
# ruff: noqa: UP006 UP007
|
2
|
+
"""
|
3
|
+
TODO:
|
4
|
+
- yum/rpm
|
5
|
+
"""
|
6
|
+
import abc
|
7
|
+
import dataclasses as dc
|
8
|
+
import json
|
9
|
+
import os
|
10
|
+
import typing as ta
|
11
|
+
|
12
|
+
from omlish.lite.asyncio.subprocesses import asyncio_subprocess_check_call
|
13
|
+
from omlish.lite.asyncio.subprocesses import asyncio_subprocess_check_output
|
14
|
+
from omlish.lite.asyncio.subprocesses import asyncio_subprocess_run
|
15
|
+
from omlish.lite.check import check
|
16
|
+
|
17
|
+
|
18
|
+
SystemPackageOrStr = ta.Union['SystemPackage', str]
|
19
|
+
|
20
|
+
|
21
|
+
@dc.dataclass(frozen=True)
|
22
|
+
class SystemPackage:
|
23
|
+
name: str
|
24
|
+
version: ta.Optional[str] = None
|
25
|
+
|
26
|
+
|
27
|
+
class SystemPackageManager(abc.ABC):
|
28
|
+
@abc.abstractmethod
|
29
|
+
def update(self) -> ta.Awaitable[None]:
|
30
|
+
raise NotImplementedError
|
31
|
+
|
32
|
+
@abc.abstractmethod
|
33
|
+
def upgrade(self) -> ta.Awaitable[None]:
|
34
|
+
raise NotImplementedError
|
35
|
+
|
36
|
+
@abc.abstractmethod
|
37
|
+
def install(self, *packages: SystemPackageOrStr) -> ta.Awaitable[None]:
|
38
|
+
raise NotImplementedError
|
39
|
+
|
40
|
+
@abc.abstractmethod
|
41
|
+
def query(self, *packages: SystemPackageOrStr) -> ta.Awaitable[ta.Mapping[str, SystemPackage]]:
|
42
|
+
raise NotImplementedError
|
43
|
+
|
44
|
+
|
45
|
+
class BrewSystemPackageManager(SystemPackageManager):
|
46
|
+
async def update(self) -> None:
|
47
|
+
await asyncio_subprocess_check_call('brew', 'update')
|
48
|
+
|
49
|
+
async def upgrade(self) -> None:
|
50
|
+
await asyncio_subprocess_check_call('brew', 'upgrade')
|
51
|
+
|
52
|
+
async def install(self, *packages: SystemPackageOrStr) -> None:
|
53
|
+
es: ta.List[str] = []
|
54
|
+
for p in packages:
|
55
|
+
if isinstance(p, SystemPackage):
|
56
|
+
es.append(p.name + (f'@{p.version}' if p.version is not None else ''))
|
57
|
+
else:
|
58
|
+
es.append(p)
|
59
|
+
await asyncio_subprocess_check_call('brew', 'install', *es)
|
60
|
+
|
61
|
+
async def query(self, *packages: SystemPackageOrStr) -> ta.Mapping[str, SystemPackage]:
|
62
|
+
pns = [p.name if isinstance(p, SystemPackage) else p for p in packages]
|
63
|
+
o = await asyncio_subprocess_check_output('brew', 'info', '--json', *pns)
|
64
|
+
j = json.loads(o.decode())
|
65
|
+
d: ta.Dict[str, SystemPackage] = {}
|
66
|
+
for e in j:
|
67
|
+
if not e['installed']:
|
68
|
+
continue
|
69
|
+
d[e['name']] = SystemPackage(
|
70
|
+
name=e['name'],
|
71
|
+
version=e['installed'][0]['version'],
|
72
|
+
)
|
73
|
+
return d
|
74
|
+
|
75
|
+
|
76
|
+
class AptSystemPackageManager(SystemPackageManager):
|
77
|
+
_APT_ENV: ta.ClassVar[ta.Mapping[str, str]] = {
|
78
|
+
'DEBIAN_FRONTEND': 'noninteractive',
|
79
|
+
}
|
80
|
+
|
81
|
+
async def update(self) -> None:
|
82
|
+
await asyncio_subprocess_check_call('apt', 'update', env={**os.environ, **self._APT_ENV})
|
83
|
+
|
84
|
+
async def upgrade(self) -> None:
|
85
|
+
await asyncio_subprocess_check_call('apt', 'upgrade', '-y', env={**os.environ, **self._APT_ENV})
|
86
|
+
|
87
|
+
async def install(self, *packages: SystemPackageOrStr) -> None:
|
88
|
+
pns = [p.name if isinstance(p, SystemPackage) else p for p in packages] # FIXME: versions
|
89
|
+
await asyncio_subprocess_check_call('apt', 'install', '-y', *pns, env={**os.environ, **self._APT_ENV})
|
90
|
+
|
91
|
+
async def query(self, *packages: SystemPackageOrStr) -> ta.Mapping[str, SystemPackage]:
|
92
|
+
pns = [p.name if isinstance(p, SystemPackage) else p for p in packages]
|
93
|
+
cmd = ['dpkg-query', '-W', '-f=${Package}=${Version}\n', *pns]
|
94
|
+
stdout, stderr = await asyncio_subprocess_run(
|
95
|
+
*cmd,
|
96
|
+
capture_output=True,
|
97
|
+
check=False,
|
98
|
+
)
|
99
|
+
d: ta.Dict[str, SystemPackage] = {}
|
100
|
+
for l in check.not_none(stdout).decode('utf-8').strip().splitlines():
|
101
|
+
n, v = l.split('=', 1)
|
102
|
+
d[n] = SystemPackage(
|
103
|
+
name=n,
|
104
|
+
version=v,
|
105
|
+
)
|
106
|
+
return d
|