ominfra 0.0.0.dev154__py3-none-any.whl → 0.0.0.dev156__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. ominfra/manage/bootstrap.py +4 -0
  2. ominfra/manage/bootstrap_.py +5 -0
  3. ominfra/manage/commands/inject.py +8 -11
  4. ominfra/manage/commands/{execution.py → local.py} +1 -5
  5. ominfra/manage/commands/ping.py +23 -0
  6. ominfra/manage/commands/subprocess.py +3 -4
  7. ominfra/manage/commands/types.py +8 -0
  8. ominfra/manage/deploy/apps.py +72 -0
  9. ominfra/manage/deploy/config.py +8 -0
  10. ominfra/manage/deploy/git.py +136 -0
  11. ominfra/manage/deploy/inject.py +21 -0
  12. ominfra/manage/deploy/paths.py +81 -28
  13. ominfra/manage/deploy/types.py +13 -0
  14. ominfra/manage/deploy/venvs.py +66 -0
  15. ominfra/manage/inject.py +20 -4
  16. ominfra/manage/main.py +15 -27
  17. ominfra/manage/remote/_main.py +1 -1
  18. ominfra/manage/remote/config.py +0 -2
  19. ominfra/manage/remote/connection.py +7 -24
  20. ominfra/manage/remote/execution.py +1 -1
  21. ominfra/manage/remote/inject.py +3 -14
  22. ominfra/manage/remote/spawning.py +2 -2
  23. ominfra/manage/system/commands.py +22 -2
  24. ominfra/manage/system/config.py +3 -1
  25. ominfra/manage/system/inject.py +16 -6
  26. ominfra/manage/system/packages.py +38 -14
  27. ominfra/manage/system/platforms.py +72 -0
  28. ominfra/manage/targets/__init__.py +0 -0
  29. ominfra/manage/targets/connection.py +150 -0
  30. ominfra/manage/targets/inject.py +42 -0
  31. ominfra/manage/targets/targets.py +87 -0
  32. ominfra/scripts/journald2aws.py +205 -134
  33. ominfra/scripts/manage.py +2192 -734
  34. ominfra/scripts/supervisor.py +187 -25
  35. ominfra/supervisor/configs.py +163 -18
  36. {ominfra-0.0.0.dev154.dist-info → ominfra-0.0.0.dev156.dist-info}/METADATA +3 -3
  37. {ominfra-0.0.0.dev154.dist-info → ominfra-0.0.0.dev156.dist-info}/RECORD +42 -31
  38. ominfra/manage/system/types.py +0 -5
  39. /ominfra/manage/{commands → deploy}/interp.py +0 -0
  40. {ominfra-0.0.0.dev154.dist-info → ominfra-0.0.0.dev156.dist-info}/LICENSE +0 -0
  41. {ominfra-0.0.0.dev154.dist-info → ominfra-0.0.0.dev156.dist-info}/WHEEL +0 -0
  42. {ominfra-0.0.0.dev154.dist-info → ominfra-0.0.0.dev156.dist-info}/entry_points.txt +0 -0
  43. {ominfra-0.0.0.dev154.dist-info → ominfra-0.0.0.dev156.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,66 @@
1
+ # ruff: noqa: UP006 UP007
2
+ """
3
+ TODO:
4
+ - interp
5
+ - share more code with pyproject?
6
+ """
7
+ import os.path
8
+ import typing as ta
9
+
10
+ from omlish.lite.asyncio.subprocesses import asyncio_subprocesses
11
+
12
+ from .paths import DeployPath
13
+ from .paths import DeployPathOwner
14
+ from .types import DeployAppTag
15
+ from .types import DeployHome
16
+
17
+
18
+ class DeployVenvManager(DeployPathOwner):
19
+ def __init__(
20
+ self,
21
+ *,
22
+ deploy_home: DeployHome,
23
+ ) -> None:
24
+ super().__init__()
25
+
26
+ self._deploy_home = deploy_home
27
+ self._dir = os.path.join(deploy_home, 'venvs')
28
+
29
+ def get_deploy_paths(self) -> ta.AbstractSet[DeployPath]:
30
+ return {
31
+ DeployPath.parse('venvs/@app/@tag/'),
32
+ }
33
+
34
+ async def setup_venv(
35
+ self,
36
+ app_dir: str,
37
+ venv_dir: str,
38
+ *,
39
+ use_uv: bool = True,
40
+ ) -> None:
41
+ sys_exe = 'python3'
42
+
43
+ await asyncio_subprocesses.check_call(sys_exe, '-m', 'venv', venv_dir)
44
+
45
+ #
46
+
47
+ venv_exe = os.path.join(venv_dir, 'bin', 'python3')
48
+
49
+ #
50
+
51
+ reqs_txt = os.path.join(app_dir, 'requirements.txt')
52
+
53
+ if os.path.isfile(reqs_txt):
54
+ if use_uv:
55
+ await asyncio_subprocesses.check_call(venv_exe, '-m', 'pip', 'install', 'uv')
56
+ pip_cmd = ['-m', 'uv', 'pip']
57
+ else:
58
+ pip_cmd = ['-m', 'pip']
59
+
60
+ await asyncio_subprocesses.check_call(venv_exe, *pip_cmd,'install', '-r', reqs_txt)
61
+
62
+ async def setup_app_venv(self, app_tag: DeployAppTag) -> None:
63
+ await self.setup_venv(
64
+ os.path.join(self._deploy_home, 'apps', app_tag.app, app_tag.tag),
65
+ os.path.join(self._deploy_home, 'venvs', app_tag.app, app_tag.tag),
66
+ )
ominfra/manage/inject.py CHANGED
@@ -6,8 +6,10 @@ from omlish.lite.inject import InjectorBindings
6
6
  from omlish.lite.inject import inj
7
7
  from omlish.lite.marshal import ObjMarshalerManager
8
8
 
9
+ from .bootstrap import MainBootstrap
9
10
  from .commands.inject import bind_commands
10
11
  from .config import MainConfig
12
+ from .deploy.config import DeployConfig
11
13
  from .deploy.inject import bind_deploy
12
14
  from .marshal import ObjMarshalerInstaller
13
15
  from .marshal import ObjMarshalerInstallers
@@ -15,6 +17,7 @@ from .remote.config import RemoteConfig
15
17
  from .remote.inject import bind_remote
16
18
  from .system.config import SystemConfig
17
19
  from .system.inject import bind_system
20
+ from .targets.inject import bind_targets
18
21
 
19
22
 
20
23
  ##
@@ -22,9 +25,13 @@ from .system.inject import bind_system
22
25
 
23
26
  def bind_main(
24
27
  *,
25
- main_config: MainConfig,
26
- remote_config: RemoteConfig,
27
- system_config: SystemConfig,
28
+ main_config: MainConfig = MainConfig(),
29
+
30
+ deploy_config: DeployConfig = DeployConfig(),
31
+ remote_config: RemoteConfig = RemoteConfig(),
32
+ system_config: SystemConfig = SystemConfig(),
33
+
34
+ main_bootstrap: ta.Optional[MainBootstrap] = None,
28
35
  ) -> InjectorBindings:
29
36
  lst: ta.List[InjectorBindingOrBindings] = [
30
37
  inj.bind(main_config),
@@ -33,7 +40,9 @@ def bind_main(
33
40
  main_config=main_config,
34
41
  ),
35
42
 
36
- bind_deploy(),
43
+ bind_deploy(
44
+ deploy_config=deploy_config,
45
+ ),
37
46
 
38
47
  bind_remote(
39
48
  remote_config=remote_config,
@@ -42,10 +51,17 @@ def bind_main(
42
51
  bind_system(
43
52
  system_config=system_config,
44
53
  ),
54
+
55
+ bind_targets(),
45
56
  ]
46
57
 
47
58
  #
48
59
 
60
+ if main_bootstrap is not None:
61
+ lst.append(inj.bind(main_bootstrap))
62
+
63
+ #
64
+
49
65
  def build_obj_marshaler_manager(insts: ObjMarshalerInstallers) -> ObjMarshalerManager:
50
66
  msh = ObjMarshalerManager()
51
67
  inst: ObjMarshalerInstaller
ominfra/manage/main.py CHANGED
@@ -6,7 +6,6 @@ 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 asyncio
9
- import contextlib
10
9
  import json
11
10
  import sys
12
11
  import typing as ta
@@ -22,22 +21,17 @@ from omlish.lite.pycharm import PycharmRemoteDebug
22
21
  from .bootstrap import MainBootstrap
23
22
  from .bootstrap_ import main_bootstrap
24
23
  from .commands.base import Command
25
- from .commands.base import CommandExecutor
26
- from .commands.execution import LocalCommandExecutor
27
24
  from .config import MainConfig
25
+ from .deploy.config import DeployConfig
28
26
  from .remote.config import RemoteConfig
29
- from .remote.connection import RemoteExecutionConnector
30
- from .remote.spawning import RemoteSpawning
27
+ from .targets.connection import ManageTargetConnector
28
+ from .targets.targets import ManageTarget
31
29
 
32
30
 
33
31
  class MainCli(ArgparseCli):
34
32
  @argparse_command(
35
33
  argparse_arg('--_payload-file'),
36
34
 
37
- argparse_arg('-s', '--shell'),
38
- argparse_arg('-q', '--shell-quote', action='store_true'),
39
- argparse_arg('--python', default='python3'),
40
-
41
35
  argparse_arg('--pycharm-debug-port', type=int),
42
36
  argparse_arg('--pycharm-debug-host'),
43
37
  argparse_arg('--pycharm-debug-version'),
@@ -46,8 +40,9 @@ class MainCli(ArgparseCli):
46
40
 
47
41
  argparse_arg('--debug', action='store_true'),
48
42
 
49
- argparse_arg('--local', action='store_true'),
43
+ argparse_arg('--deploy-home'),
50
44
 
45
+ argparse_arg('target'),
51
46
  argparse_arg('command', nargs='+'),
52
47
  )
53
48
  async def run(self) -> None:
@@ -58,6 +53,10 @@ class MainCli(ArgparseCli):
58
53
  debug=bool(self.args.debug),
59
54
  ),
60
55
 
56
+ deploy_config=DeployConfig(
57
+ deploy_home=self.args.deploy_home,
58
+ ),
59
+
61
60
  remote_config=RemoteConfig(
62
61
  payload_file=self.args._payload_file, # noqa
63
62
 
@@ -68,8 +67,6 @@ class MainCli(ArgparseCli):
68
67
  ) if self.args.pycharm_debug_port is not None else None,
69
68
 
70
69
  timebomb_delay_s=self.args.remote_timebomb_delay_s,
71
-
72
- use_in_process_remote_executor=True,
73
70
  ),
74
71
  )
75
72
 
@@ -83,6 +80,11 @@ class MainCli(ArgparseCli):
83
80
 
84
81
  msh = injector[ObjMarshalerManager]
85
82
 
83
+ ts = self.args.target
84
+ if not ts.startswith('{'):
85
+ ts = json.dumps({ts: {}})
86
+ tgt: ManageTarget = msh.unmarshal_obj(json.loads(ts), ManageTarget)
87
+
86
88
  cmds: ta.List[Command] = []
87
89
  cmd: Command
88
90
  for c in self.args.command:
@@ -93,21 +95,7 @@ class MainCli(ArgparseCli):
93
95
 
94
96
  #
95
97
 
96
- async with contextlib.AsyncExitStack() as es:
97
- ce: CommandExecutor
98
-
99
- if self.args.local:
100
- ce = injector[LocalCommandExecutor]
101
-
102
- else:
103
- tgt = RemoteSpawning.Target(
104
- shell=self.args.shell,
105
- shell_quote=self.args.shell_quote,
106
- python=self.args.python,
107
- )
108
-
109
- ce = await es.enter_async_context(injector[RemoteExecutionConnector].connect(tgt, bs)) # noqa
110
-
98
+ async with injector[ManageTargetConnector].connect(tgt) as ce:
111
99
  async def run_command(cmd: Command) -> None:
112
100
  res = await ce.try_execute(
113
101
  cmd,
@@ -20,7 +20,7 @@ from omlish.os.deathsig import set_process_deathsig
20
20
 
21
21
  from ...pyremote import pyremote_bootstrap_finalize
22
22
  from ..bootstrap import MainBootstrap
23
- from ..commands.execution import LocalCommandExecutor
23
+ from ..commands.local import LocalCommandExecutor
24
24
  from .channel import RemoteChannel
25
25
  from .channel import RemoteChannelImpl
26
26
  from .execution import _RemoteCommandHandler
@@ -20,5 +20,3 @@ class RemoteConfig:
20
20
  timebomb_delay_s: ta.Optional[float] = 60 * 60.
21
21
 
22
22
  heartbeat_interval_s: float = 3.
23
-
24
- use_in_process_remote_executor: bool = False
@@ -1,18 +1,18 @@
1
1
  # ruff: noqa: UP006 UP007
2
- import abc
3
2
  import asyncio
4
3
  import contextlib
5
4
  import typing as ta
6
5
 
7
6
  from omlish.asyncs.asyncio.channels import asyncio_create_bytes_channel
8
7
  from omlish.lite.cached import cached_nullary
8
+ from omlish.lite.contextmanagers import aclosing
9
9
  from omlish.lite.marshal import ObjMarshalerManager
10
10
 
11
11
  from ...pyremote import PyremoteBootstrapDriver
12
12
  from ...pyremote import PyremoteBootstrapOptions
13
13
  from ...pyremote import pyremote_build_bootstrap_cmd
14
14
  from ..bootstrap import MainBootstrap
15
- from ..commands.execution import LocalCommandExecutor
15
+ from ..commands.local import LocalCommandExecutor
16
16
  from ._main import _remote_execution_main # noqa
17
17
  from .channel import RemoteChannelImpl
18
18
  from .execution import RemoteCommandExecutor
@@ -25,20 +25,7 @@ from .spawning import RemoteSpawning
25
25
  ##
26
26
 
27
27
 
28
- class RemoteExecutionConnector(abc.ABC):
29
- @abc.abstractmethod
30
- def connect(
31
- self,
32
- tgt: RemoteSpawning.Target,
33
- bs: MainBootstrap,
34
- ) -> ta.AsyncContextManager[RemoteCommandExecutor]:
35
- raise NotImplementedError
36
-
37
-
38
- ##
39
-
40
-
41
- class PyremoteRemoteExecutionConnector(RemoteExecutionConnector):
28
+ class PyremoteRemoteExecutionConnector:
42
29
  def __init__(
43
30
  self,
44
31
  *,
@@ -104,7 +91,7 @@ class PyremoteRemoteExecutionConnector(RemoteExecutionConnector):
104
91
  await chan.send_obj(bs)
105
92
 
106
93
  rce: RemoteCommandExecutor
107
- async with contextlib.aclosing(RemoteCommandExecutor(chan)) as rce:
94
+ async with aclosing(RemoteCommandExecutor(chan)) as rce:
108
95
  await rce.start()
109
96
 
110
97
  yield rce
@@ -113,7 +100,7 @@ class PyremoteRemoteExecutionConnector(RemoteExecutionConnector):
113
100
  ##
114
101
 
115
102
 
116
- class InProcessRemoteExecutionConnector(RemoteExecutionConnector):
103
+ class InProcessRemoteExecutionConnector:
117
104
  def __init__(
118
105
  self,
119
106
  *,
@@ -126,11 +113,7 @@ class InProcessRemoteExecutionConnector(RemoteExecutionConnector):
126
113
  self._local_executor = local_executor
127
114
 
128
115
  @contextlib.asynccontextmanager
129
- async def connect(
130
- self,
131
- tgt: RemoteSpawning.Target,
132
- bs: MainBootstrap,
133
- ) -> ta.AsyncGenerator[RemoteCommandExecutor, None]:
116
+ async def connect(self) -> ta.AsyncGenerator[RemoteCommandExecutor, None]:
134
117
  r0, w0 = asyncio_create_bytes_channel()
135
118
  r1, w1 = asyncio_create_bytes_channel()
136
119
 
@@ -144,7 +127,7 @@ class InProcessRemoteExecutionConnector(RemoteExecutionConnector):
144
127
  rch_task = asyncio.create_task(rch.run()) # noqa
145
128
  try:
146
129
  rce: RemoteCommandExecutor
147
- async with contextlib.aclosing(RemoteCommandExecutor(local_chan)) as rce:
130
+ async with aclosing(RemoteCommandExecutor(local_chan)) as rce:
148
131
  await rce.start()
149
132
 
150
133
  yield rce
@@ -394,7 +394,7 @@ class RemoteCommandExecutor(CommandExecutor):
394
394
  self,
395
395
  cmd: Command,
396
396
  *,
397
- log: ta.Optional[logging.Logger] = None,
397
+ log: ta.Optional[logging.Logger] = None, # noqa
398
398
  omit_exc_object: bool = False,
399
399
  ) -> CommandOutputOrException:
400
400
  try:
@@ -8,7 +8,6 @@ from omlish.lite.inject import inj
8
8
  from .config import RemoteConfig
9
9
  from .connection import InProcessRemoteExecutionConnector
10
10
  from .connection import PyremoteRemoteExecutionConnector
11
- from .connection import RemoteExecutionConnector
12
11
  from .payload import RemoteExecutionPayloadFile
13
12
  from .spawning import RemoteSpawning
14
13
  from .spawning import SubprocessRemoteSpawning
@@ -23,20 +22,10 @@ def bind_remote(
23
22
 
24
23
  inj.bind(SubprocessRemoteSpawning, singleton=True),
25
24
  inj.bind(RemoteSpawning, to_key=SubprocessRemoteSpawning),
26
- ]
27
-
28
- #
29
25
 
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
- ])
26
+ inj.bind(PyremoteRemoteExecutionConnector, singleton=True),
27
+ inj.bind(InProcessRemoteExecutionConnector, singleton=True),
28
+ ]
40
29
 
41
30
  #
42
31
 
@@ -7,7 +7,7 @@ import shlex
7
7
  import subprocess
8
8
  import typing as ta
9
9
 
10
- from omlish.lite.asyncio.subprocesses import asyncio_subprocess_popen
10
+ from omlish.lite.asyncio.subprocesses import asyncio_subprocesses
11
11
  from omlish.lite.check import check
12
12
  from omlish.lite.subprocesses import SUBPROCESS_CHANNEL_OPTION_VALUES
13
13
  from omlish.lite.subprocesses import SubprocessChannelOption
@@ -86,7 +86,7 @@ class SubprocessRemoteSpawning(RemoteSpawning):
86
86
  if not debug:
87
87
  cmd = subprocess_maybe_shell_wrap_exec(*cmd)
88
88
 
89
- async with asyncio_subprocess_popen(
89
+ async with asyncio_subprocesses.popen(
90
90
  *cmd,
91
91
  shell=pc.shell,
92
92
  stdin=subprocess.PIPE,
@@ -1,10 +1,14 @@
1
1
  # ruff: noqa: UP006 UP007
2
2
  import dataclasses as dc
3
+ import typing as ta
3
4
 
5
+ from omlish.lite.check import check
4
6
  from omlish.lite.logs import log
5
7
 
6
8
  from ..commands.base import Command
7
9
  from ..commands.base import CommandExecutor
10
+ from .packages import SystemPackage
11
+ from .packages import SystemPackageManager
8
12
 
9
13
 
10
14
  ##
@@ -12,13 +16,29 @@ from ..commands.base import CommandExecutor
12
16
 
13
17
  @dc.dataclass(frozen=True)
14
18
  class CheckSystemPackageCommand(Command['CheckSystemPackageCommand.Output']):
19
+ pkgs: ta.Sequence[str] = ()
20
+
21
+ def __post_init__(self) -> None:
22
+ check.not_isinstance(self.pkgs, str)
23
+
15
24
  @dc.dataclass(frozen=True)
16
25
  class Output(Command.Output):
17
- pass
26
+ pkgs: ta.Sequence[SystemPackage]
18
27
 
19
28
 
20
29
  class CheckSystemPackageCommandExecutor(CommandExecutor[CheckSystemPackageCommand, CheckSystemPackageCommand.Output]):
30
+ def __init__(
31
+ self,
32
+ *,
33
+ mgr: SystemPackageManager,
34
+ ) -> None:
35
+ super().__init__()
36
+
37
+ self._mgr = mgr
38
+
21
39
  async def execute(self, cmd: CheckSystemPackageCommand) -> CheckSystemPackageCommand.Output:
22
40
  log.info('Checking system package!')
23
41
 
24
- return CheckSystemPackageCommand.Output()
42
+ ret = await self._mgr.query(*cmd.pkgs)
43
+
44
+ return CheckSystemPackageCommand.Output(list(ret.values()))
@@ -2,7 +2,9 @@
2
2
  import dataclasses as dc
3
3
  import typing as ta
4
4
 
5
+ from .platforms import Platform
6
+
5
7
 
6
8
  @dc.dataclass(frozen=True)
7
9
  class SystemConfig:
8
- platform: ta.Optional[str] = None
10
+ platform: ta.Optional[Platform] = None
@@ -1,5 +1,4 @@
1
1
  # ruff: noqa: UP006 UP007
2
- import sys
3
2
  import typing as ta
4
3
 
5
4
  from omlish.lite.inject import InjectorBindingOrBindings
@@ -13,7 +12,12 @@ from .config import SystemConfig
13
12
  from .packages import AptSystemPackageManager
14
13
  from .packages import BrewSystemPackageManager
15
14
  from .packages import SystemPackageManager
16
- from .types import SystemPlatform
15
+ from .packages import YumSystemPackageManager
16
+ from .platforms import AmazonLinuxPlatform
17
+ from .platforms import DarwinPlatform
18
+ from .platforms import LinuxPlatform
19
+ from .platforms import Platform
20
+ from .platforms import detect_system_platform
17
21
 
18
22
 
19
23
  def bind_system(
@@ -26,18 +30,24 @@ def bind_system(
26
30
 
27
31
  #
28
32
 
29
- platform = system_config.platform or sys.platform
30
- lst.append(inj.bind(platform, key=SystemPlatform))
33
+ platform = system_config.platform or detect_system_platform()
34
+ lst.append(inj.bind(platform, key=Platform))
31
35
 
32
36
  #
33
37
 
34
- if platform == 'linux':
38
+ if isinstance(platform, AmazonLinuxPlatform):
39
+ lst.extend([
40
+ inj.bind(YumSystemPackageManager, singleton=True),
41
+ inj.bind(SystemPackageManager, to_key=YumSystemPackageManager),
42
+ ])
43
+
44
+ elif isinstance(platform, LinuxPlatform):
35
45
  lst.extend([
36
46
  inj.bind(AptSystemPackageManager, singleton=True),
37
47
  inj.bind(SystemPackageManager, to_key=AptSystemPackageManager),
38
48
  ])
39
49
 
40
- elif platform == 'darwin':
50
+ elif isinstance(platform, DarwinPlatform):
41
51
  lst.extend([
42
52
  inj.bind(BrewSystemPackageManager, singleton=True),
43
53
  inj.bind(SystemPackageManager, to_key=BrewSystemPackageManager),
@@ -9,9 +9,7 @@ import json
9
9
  import os
10
10
  import typing as ta
11
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
12
+ from omlish.lite.asyncio.subprocesses import asyncio_subprocesses
15
13
  from omlish.lite.check import check
16
14
 
17
15
 
@@ -44,10 +42,10 @@ class SystemPackageManager(abc.ABC):
44
42
 
45
43
  class BrewSystemPackageManager(SystemPackageManager):
46
44
  async def update(self) -> None:
47
- await asyncio_subprocess_check_call('brew', 'update')
45
+ await asyncio_subprocesses.check_call('brew', 'update')
48
46
 
49
47
  async def upgrade(self) -> None:
50
- await asyncio_subprocess_check_call('brew', 'upgrade')
48
+ await asyncio_subprocesses.check_call('brew', 'upgrade')
51
49
 
52
50
  async def install(self, *packages: SystemPackageOrStr) -> None:
53
51
  es: ta.List[str] = []
@@ -56,11 +54,11 @@ class BrewSystemPackageManager(SystemPackageManager):
56
54
  es.append(p.name + (f'@{p.version}' if p.version is not None else ''))
57
55
  else:
58
56
  es.append(p)
59
- await asyncio_subprocess_check_call('brew', 'install', *es)
57
+ await asyncio_subprocesses.check_call('brew', 'install', *es)
60
58
 
61
59
  async def query(self, *packages: SystemPackageOrStr) -> ta.Mapping[str, SystemPackage]:
62
60
  pns = [p.name if isinstance(p, SystemPackage) else p for p in packages]
63
- o = await asyncio_subprocess_check_output('brew', 'info', '--json', *pns)
61
+ o = await asyncio_subprocesses.check_output('brew', 'info', '--json', *pns)
64
62
  j = json.loads(o.decode())
65
63
  d: ta.Dict[str, SystemPackage] = {}
66
64
  for e in j:
@@ -79,28 +77,54 @@ class AptSystemPackageManager(SystemPackageManager):
79
77
  }
80
78
 
81
79
  async def update(self) -> None:
82
- await asyncio_subprocess_check_call('apt', 'update', env={**os.environ, **self._APT_ENV})
80
+ await asyncio_subprocesses.check_call('sudo', 'apt', 'update', env={**os.environ, **self._APT_ENV})
83
81
 
84
82
  async def upgrade(self) -> None:
85
- await asyncio_subprocess_check_call('apt', 'upgrade', '-y', env={**os.environ, **self._APT_ENV})
83
+ await asyncio_subprocesses.check_call('sudo', 'apt', 'upgrade', '-y', env={**os.environ, **self._APT_ENV})
86
84
 
87
85
  async def install(self, *packages: SystemPackageOrStr) -> None:
88
86
  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})
87
+ await asyncio_subprocesses.check_call('sudo', 'apt', 'install', '-y', *pns, env={**os.environ, **self._APT_ENV})
90
88
 
91
89
  async def query(self, *packages: SystemPackageOrStr) -> ta.Mapping[str, SystemPackage]:
92
90
  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,
91
+ out = await asyncio_subprocesses.run(
92
+ 'dpkg-query', '-W', '-f=${Package}=${Version}\n', *pns,
96
93
  capture_output=True,
97
94
  check=False,
98
95
  )
99
96
  d: ta.Dict[str, SystemPackage] = {}
100
- for l in check.not_none(stdout).decode('utf-8').strip().splitlines():
97
+ for l in check.not_none(out.stdout).decode('utf-8').strip().splitlines():
101
98
  n, v = l.split('=', 1)
102
99
  d[n] = SystemPackage(
103
100
  name=n,
104
101
  version=v,
105
102
  )
106
103
  return d
104
+
105
+
106
+ class YumSystemPackageManager(SystemPackageManager):
107
+ async def update(self) -> None:
108
+ await asyncio_subprocesses.check_call('sudo', 'yum', 'check-update')
109
+
110
+ async def upgrade(self) -> None:
111
+ await asyncio_subprocesses.check_call('sudo', 'yum', 'update')
112
+
113
+ async def install(self, *packages: SystemPackageOrStr) -> None:
114
+ pns = [p.name if isinstance(p, SystemPackage) else p for p in packages] # FIXME: versions
115
+ await asyncio_subprocesses.check_call('sudo', 'yum', 'install', *pns)
116
+
117
+ async def query(self, *packages: SystemPackageOrStr) -> ta.Mapping[str, SystemPackage]:
118
+ pns = [p.name if isinstance(p, SystemPackage) else p for p in packages]
119
+ d: ta.Dict[str, SystemPackage] = {}
120
+ for pn in pns:
121
+ out = await asyncio_subprocesses.run(
122
+ 'rpm', '-q', pn,
123
+ capture_output=True,
124
+ )
125
+ if not out.proc.returncode:
126
+ d[pn] = SystemPackage(
127
+ pn,
128
+ check.not_none(out.stdout).decode().strip(),
129
+ )
130
+ return d
@@ -0,0 +1,72 @@
1
+ import abc
2
+ import dataclasses as dc
3
+ import sys
4
+
5
+ from omlish.lite.cached import cached_nullary
6
+ from omlish.lite.logs import log
7
+ from omlish.os.linux import LinuxOsRelease
8
+
9
+
10
+ ##
11
+
12
+
13
+ @dc.dataclass(frozen=True)
14
+ class Platform(abc.ABC): # noqa
15
+ pass
16
+
17
+
18
+ class LinuxPlatform(Platform, abc.ABC):
19
+ pass
20
+
21
+
22
+ class UbuntuPlatform(LinuxPlatform):
23
+ pass
24
+
25
+
26
+ class AmazonLinuxPlatform(LinuxPlatform):
27
+ pass
28
+
29
+
30
+ class GenericLinuxPlatform(LinuxPlatform):
31
+ pass
32
+
33
+
34
+ class DarwinPlatform(Platform):
35
+ pass
36
+
37
+
38
+ class UnknownPlatform(Platform):
39
+ pass
40
+
41
+
42
+ ##
43
+
44
+
45
+ def _detect_system_platform() -> Platform:
46
+ plat = sys.platform
47
+
48
+ if plat == 'linux':
49
+ if (osr := LinuxOsRelease.read()) is None:
50
+ return GenericLinuxPlatform()
51
+
52
+ if osr.id == 'amzn':
53
+ return AmazonLinuxPlatform()
54
+
55
+ elif osr.id == 'ubuntu':
56
+ return UbuntuPlatform()
57
+
58
+ else:
59
+ return GenericLinuxPlatform()
60
+
61
+ elif plat == 'darwin':
62
+ return DarwinPlatform()
63
+
64
+ else:
65
+ return UnknownPlatform()
66
+
67
+
68
+ @cached_nullary
69
+ def detect_system_platform() -> Platform:
70
+ platform = _detect_system_platform()
71
+ log.info('Detected platform: %r', platform)
72
+ return platform
File without changes