ominfra 0.0.0.dev145__tar.gz → 0.0.0.dev146__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
Files changed (113) hide show
  1. {ominfra-0.0.0.dev145/ominfra.egg-info → ominfra-0.0.0.dev146}/PKG-INFO +3 -3
  2. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/manage/commands/inject.py +5 -0
  3. ominfra-0.0.0.dev146/ominfra/manage/commands/interp.py +39 -0
  4. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/manage/commands/subprocess.py +2 -2
  5. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/manage/deploy/command.py +4 -0
  6. ominfra-0.0.0.dev146/ominfra/manage/deploy/paths.py +180 -0
  7. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/manage/main.py +14 -8
  8. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/manage/remote/channel.py +13 -2
  9. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/manage/remote/execution.py +64 -8
  10. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/scripts/manage.py +2198 -238
  11. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146/ominfra.egg-info}/PKG-INFO +3 -3
  12. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra.egg-info/SOURCES.txt +1 -0
  13. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra.egg-info/requires.txt +2 -2
  14. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/pyproject.toml +3 -3
  15. ominfra-0.0.0.dev145/ominfra/manage/deploy/paths.py +0 -33
  16. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/LICENSE +0 -0
  17. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/MANIFEST.in +0 -0
  18. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/README.rst +0 -0
  19. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/.manifests.json +0 -0
  20. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/__about__.py +0 -0
  21. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/__init__.py +0 -0
  22. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/clouds/__init__.py +0 -0
  23. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/clouds/aws/__init__.py +0 -0
  24. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/clouds/aws/__main__.py +0 -0
  25. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/clouds/aws/auth.py +0 -0
  26. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/clouds/aws/cli.py +0 -0
  27. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/clouds/aws/dataclasses.py +0 -0
  28. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/clouds/aws/journald2aws/__init__.py +0 -0
  29. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/clouds/aws/journald2aws/__main__.py +0 -0
  30. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/clouds/aws/journald2aws/cursor.py +0 -0
  31. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/clouds/aws/journald2aws/driver.py +0 -0
  32. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/clouds/aws/journald2aws/main.py +0 -0
  33. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/clouds/aws/journald2aws/poster.py +0 -0
  34. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/clouds/aws/logs.py +0 -0
  35. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/clouds/aws/metadata.py +0 -0
  36. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/clouds/gcp/__init__.py +0 -0
  37. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/clouds/gcp/auth.py +0 -0
  38. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/cmds.py +0 -0
  39. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/configs.py +0 -0
  40. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/journald/__init__.py +0 -0
  41. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/journald/fields.py +0 -0
  42. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/journald/genmessages.py +0 -0
  43. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/journald/messages.py +0 -0
  44. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/journald/tailer.py +0 -0
  45. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/manage/__init__.py +0 -0
  46. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/manage/__main__.py +0 -0
  47. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/manage/bootstrap.py +0 -0
  48. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/manage/bootstrap_.py +0 -0
  49. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/manage/commands/__init__.py +0 -0
  50. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/manage/commands/base.py +0 -0
  51. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/manage/commands/execution.py +0 -0
  52. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/manage/commands/marshal.py +0 -0
  53. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/manage/config.py +0 -0
  54. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/manage/deploy/__init__.py +0 -0
  55. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/manage/deploy/inject.py +0 -0
  56. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/manage/inject.py +0 -0
  57. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/manage/marshal.py +0 -0
  58. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/manage/remote/__init__.py +0 -0
  59. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/manage/remote/config.py +0 -0
  60. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/manage/remote/inject.py +0 -0
  61. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/manage/remote/payload.py +0 -0
  62. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/manage/remote/spawning.py +0 -0
  63. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/pyremote.py +0 -0
  64. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/scripts/__init__.py +0 -0
  65. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/scripts/journald2aws.py +0 -0
  66. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/scripts/supervisor.py +0 -0
  67. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/ssh.py +0 -0
  68. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/supervisor/LICENSE.txt +0 -0
  69. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/supervisor/__init__.py +0 -0
  70. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/supervisor/__main__.py +0 -0
  71. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/supervisor/configs.py +0 -0
  72. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/supervisor/dispatchers.py +0 -0
  73. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/supervisor/dispatchersimpl.py +0 -0
  74. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/supervisor/events.py +0 -0
  75. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/supervisor/exceptions.py +0 -0
  76. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/supervisor/groups.py +0 -0
  77. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/supervisor/groupsimpl.py +0 -0
  78. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/supervisor/http.py +0 -0
  79. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/supervisor/inject.py +0 -0
  80. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/supervisor/io.py +0 -0
  81. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/supervisor/main.py +0 -0
  82. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/supervisor/pipes.py +0 -0
  83. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/supervisor/privileges.py +0 -0
  84. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/supervisor/process.py +0 -0
  85. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/supervisor/processimpl.py +0 -0
  86. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/supervisor/setup.py +0 -0
  87. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/supervisor/setupimpl.py +0 -0
  88. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/supervisor/signals.py +0 -0
  89. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/supervisor/spawning.py +0 -0
  90. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/supervisor/spawningimpl.py +0 -0
  91. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/supervisor/states.py +0 -0
  92. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/supervisor/supervisor.py +0 -0
  93. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/supervisor/types.py +0 -0
  94. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/supervisor/utils/__init__.py +0 -0
  95. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/supervisor/utils/collections.py +0 -0
  96. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/supervisor/utils/diag.py +0 -0
  97. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/supervisor/utils/fds.py +0 -0
  98. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/supervisor/utils/fs.py +0 -0
  99. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/supervisor/utils/os.py +0 -0
  100. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/supervisor/utils/ostypes.py +0 -0
  101. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/supervisor/utils/signals.py +0 -0
  102. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/supervisor/utils/strings.py +0 -0
  103. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/supervisor/utils/users.py +0 -0
  104. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/tailscale/__init__.py +0 -0
  105. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/tailscale/api.py +0 -0
  106. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/tailscale/cli.py +0 -0
  107. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/threadworkers.py +0 -0
  108. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/tools/__init__.py +0 -0
  109. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra/tools/listresources.py +0 -0
  110. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra.egg-info/dependency_links.txt +0 -0
  111. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra.egg-info/entry_points.txt +0 -0
  112. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/ominfra.egg-info/top_level.txt +0 -0
  113. {ominfra-0.0.0.dev145 → ominfra-0.0.0.dev146}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ominfra
3
- Version: 0.0.0.dev145
3
+ Version: 0.0.0.dev146
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.dev145
16
- Requires-Dist: omlish==0.0.0.dev145
15
+ Requires-Dist: omdev==0.0.0.dev146
16
+ Requires-Dist: omlish==0.0.0.dev146
17
17
  Provides-Extra: all
18
18
  Requires-Dist: paramiko~=3.5; extra == "all"
19
19
  Requires-Dist: asyncssh~=2.18; extra == "all"
@@ -20,6 +20,8 @@ from .base import CommandRegistrations
20
20
  from .base import build_command_name_map
21
21
  from .execution import CommandExecutorMap
22
22
  from .execution import LocalCommandExecutor
23
+ from .interp import InterpCommand
24
+ from .interp import InterpCommandExecutor
23
25
  from .marshal import install_command_marshaling
24
26
  from .subprocess import SubprocessCommand
25
27
  from .subprocess import SubprocessCommandExecutor
@@ -111,8 +113,11 @@ def bind_commands(
111
113
 
112
114
  #
113
115
 
116
+ command_cls: ta.Any
117
+ executor_cls: ta.Any
114
118
  for command_cls, executor_cls in [
115
119
  (SubprocessCommand, SubprocessCommandExecutor),
120
+ (InterpCommand, InterpCommandExecutor),
116
121
  ]:
117
122
  lst.append(bind_command(command_cls, executor_cls))
118
123
 
@@ -0,0 +1,39 @@
1
+ # ruff: noqa: UP006 UP007
2
+ import dataclasses as dc
3
+
4
+ from omdev.interp.resolvers import DEFAULT_INTERP_RESOLVER
5
+ from omdev.interp.types import InterpOpts
6
+ from omdev.interp.types import InterpSpecifier
7
+ from omlish.lite.check import check_not_none
8
+
9
+ from ..commands.base import Command
10
+ from ..commands.base import CommandExecutor
11
+
12
+
13
+ ##
14
+
15
+
16
+ @dc.dataclass(frozen=True)
17
+ class InterpCommand(Command['InterpCommand.Output']):
18
+ spec: str
19
+ install: bool = False
20
+
21
+ @dc.dataclass(frozen=True)
22
+ class Output(Command.Output):
23
+ exe: str
24
+ version: str
25
+ opts: InterpOpts
26
+
27
+
28
+ ##
29
+
30
+
31
+ class InterpCommandExecutor(CommandExecutor[InterpCommand, InterpCommand.Output]):
32
+ def execute(self, cmd: InterpCommand) -> InterpCommand.Output:
33
+ i = InterpSpecifier.parse(check_not_none(cmd.spec))
34
+ o = check_not_none(DEFAULT_INTERP_RESOLVER.resolve(i, install=cmd.install))
35
+ return InterpCommand.Output(
36
+ exe=o.exe,
37
+ version=str(o.version.version),
38
+ opts=o.version.opts,
39
+ )
@@ -5,6 +5,7 @@ import subprocess
5
5
  import time
6
6
  import typing as ta
7
7
 
8
+ from omlish.lite.check import check_not_isinstance
8
9
  from omlish.lite.subprocesses import SUBPROCESS_CHANNEL_OPTION_VALUES
9
10
  from omlish.lite.subprocesses import SubprocessChannelOption
10
11
  from omlish.lite.subprocesses import subprocess_maybe_shell_wrap_exec
@@ -31,8 +32,7 @@ class SubprocessCommand(Command['SubprocessCommand.Output']):
31
32
  timeout: ta.Optional[float] = None
32
33
 
33
34
  def __post_init__(self) -> None:
34
- if isinstance(self.cmd, str):
35
- raise TypeError(self.cmd)
35
+ check_not_isinstance(self.cmd, str)
36
36
 
37
37
  @dc.dataclass(frozen=True)
38
38
  class Output(Command.Output):
@@ -1,6 +1,8 @@
1
1
  # ruff: noqa: UP006 UP007
2
2
  import dataclasses as dc
3
3
 
4
+ from omlish.lite.logs import log
5
+
4
6
  from ..commands.base import Command
5
7
  from ..commands.base import CommandExecutor
6
8
 
@@ -20,4 +22,6 @@ class DeployCommand(Command['DeployCommand.Output']):
20
22
 
21
23
  class DeployCommandExecutor(CommandExecutor[DeployCommand, DeployCommand.Output]):
22
24
  def execute(self, cmd: DeployCommand) -> DeployCommand.Output:
25
+ log.info('Deploying!')
26
+
23
27
  return DeployCommand.Output()
@@ -0,0 +1,180 @@
1
+ # ruff: noqa: UP006 UP007
2
+ """
3
+ ~deploy
4
+ deploy.pid (flock)
5
+ /app
6
+ /<appspec> - shallow clone
7
+ /conf
8
+ /env
9
+ <appspec>.env
10
+ /nginx
11
+ <appspec>.conf
12
+ /supervisor
13
+ <appspec>.conf
14
+ /venv
15
+ /<appspec>
16
+
17
+ ?
18
+ /logs
19
+ /wrmsr--omlish--<spec>
20
+
21
+ spec = <name>--<rev>--<when>
22
+
23
+ ==
24
+
25
+ for dn in [
26
+ 'app',
27
+ 'conf',
28
+ 'conf/env',
29
+ 'conf/nginx',
30
+ 'conf/supervisor',
31
+ 'venv',
32
+ ]:
33
+ """
34
+ import abc
35
+ import dataclasses as dc
36
+ import os.path
37
+ import typing as ta
38
+
39
+ from omlish.lite.check import check_equal
40
+ from omlish.lite.check import check_non_empty
41
+ from omlish.lite.check import check_non_empty_str
42
+ from omlish.lite.check import check_not_in
43
+
44
+
45
+ DeployPathKind = ta.Literal['dir', 'file'] # ta.TypeAlias
46
+
47
+
48
+ ##
49
+
50
+
51
+ DEPLOY_PATH_SPEC_PLACEHOLDER = '@'
52
+
53
+
54
+ @dc.dataclass(frozen=True)
55
+ class DeployPathPart(abc.ABC): # noqa
56
+ @property
57
+ @abc.abstractmethod
58
+ def kind(self) -> DeployPathKind:
59
+ raise NotImplementedError
60
+
61
+ @abc.abstractmethod
62
+ def render(self) -> str:
63
+ raise NotImplementedError
64
+
65
+
66
+ #
67
+
68
+
69
+ class DeployPathDir(DeployPathPart, abc.ABC):
70
+ @property
71
+ def kind(self) -> DeployPathKind:
72
+ return 'dir'
73
+
74
+ @classmethod
75
+ def parse(cls, s: str) -> 'DeployPathDir':
76
+ if DEPLOY_PATH_SPEC_PLACEHOLDER in s:
77
+ check_equal(s, DEPLOY_PATH_SPEC_PLACEHOLDER)
78
+ return SpecDeployPathDir()
79
+ else:
80
+ return ConstDeployPathDir(s)
81
+
82
+
83
+ class DeployPathFile(DeployPathPart, abc.ABC):
84
+ @property
85
+ def kind(self) -> DeployPathKind:
86
+ return 'file'
87
+
88
+ @classmethod
89
+ def parse(cls, s: str) -> 'DeployPathFile':
90
+ if DEPLOY_PATH_SPEC_PLACEHOLDER in s:
91
+ check_equal(s[0], DEPLOY_PATH_SPEC_PLACEHOLDER)
92
+ return SpecDeployPathFile(s[1:])
93
+ else:
94
+ return ConstDeployPathFile(s)
95
+
96
+
97
+ #
98
+
99
+
100
+ @dc.dataclass(frozen=True)
101
+ class ConstDeployPathPart(DeployPathPart, abc.ABC):
102
+ name: str
103
+
104
+ def __post_init__(self) -> None:
105
+ check_non_empty_str(self.name)
106
+ check_not_in('/', self.name)
107
+ check_not_in(DEPLOY_PATH_SPEC_PLACEHOLDER, self.name)
108
+
109
+ def render(self) -> str:
110
+ return self.name
111
+
112
+
113
+ class ConstDeployPathDir(ConstDeployPathPart, DeployPathDir):
114
+ pass
115
+
116
+
117
+ class ConstDeployPathFile(ConstDeployPathPart, DeployPathFile):
118
+ pass
119
+
120
+
121
+ #
122
+
123
+
124
+ class SpecDeployPathPart(DeployPathPart, abc.ABC):
125
+ pass
126
+
127
+
128
+ class SpecDeployPathDir(SpecDeployPathPart, DeployPathDir):
129
+ def render(self) -> str:
130
+ return DEPLOY_PATH_SPEC_PLACEHOLDER
131
+
132
+
133
+ @dc.dataclass(frozen=True)
134
+ class SpecDeployPathFile(SpecDeployPathPart, DeployPathFile):
135
+ suffix: str
136
+
137
+ def __post_init__(self) -> None:
138
+ check_non_empty_str(self.suffix)
139
+ check_not_in('/', self.suffix)
140
+ check_not_in(DEPLOY_PATH_SPEC_PLACEHOLDER, self.suffix)
141
+
142
+ def render(self) -> str:
143
+ return DEPLOY_PATH_SPEC_PLACEHOLDER + self.suffix
144
+
145
+
146
+ ##
147
+
148
+
149
+ @dc.dataclass(frozen=True)
150
+ class DeployPath:
151
+ parts: ta.Sequence[DeployPathPart]
152
+
153
+ def __post_init__(self) -> None:
154
+ check_non_empty(self.parts)
155
+ for p in self.parts[:-1]:
156
+ check_equal(p.kind, 'dir')
157
+
158
+ @property
159
+ def kind(self) -> ta.Literal['file', 'dir']:
160
+ return self.parts[-1].kind
161
+
162
+ def render(self) -> str:
163
+ return os.path.join( # noqa
164
+ *[p.render() for p in self.parts],
165
+ *([''] if self.kind == 'dir' else []),
166
+ )
167
+
168
+ @classmethod
169
+ def parse(cls, s: str) -> 'DeployPath':
170
+ tail_parse: ta.Callable[[str], DeployPathPart]
171
+ if s.endswith('/'):
172
+ tail_parse = DeployPathDir.parse
173
+ s = s[:-1]
174
+ else:
175
+ tail_parse = DeployPathFile.parse
176
+ ps = check_non_empty_str(s).split('/')
177
+ return cls([
178
+ *([DeployPathDir.parse(p) for p in ps[:-1]] if len(ps) > 1 else []),
179
+ tail_parse(ps[-1]),
180
+ ])
@@ -6,6 +6,7 @@ manage.py -s 'docker run -i python:3.12'
6
6
  manage.py -s 'ssh -i /foo/bar.pem foo@bar.baz' -q --python=python3.8
7
7
  """
8
8
  import contextlib
9
+ import json
9
10
  import typing as ta
10
11
 
11
12
  from omlish.lite.logs import log # noqa
@@ -18,9 +19,7 @@ from .bootstrap_ import main_bootstrap
18
19
  from .commands.base import Command
19
20
  from .commands.base import CommandExecutor
20
21
  from .commands.execution import LocalCommandExecutor
21
- from .commands.subprocess import SubprocessCommand
22
22
  from .config import MainConfig
23
- from .deploy.command import DeployCommand
24
23
  from .remote.config import RemoteConfig
25
24
  from .remote.execution import RemoteExecution
26
25
  from .remote.spawning import RemoteSpawning
@@ -78,12 +77,15 @@ def _main() -> None:
78
77
 
79
78
  #
80
79
 
80
+ msh = injector[ObjMarshalerManager]
81
+
81
82
  cmds: ta.List[Command] = []
83
+ cmd: Command
82
84
  for c in args.command:
83
- if c == 'deploy':
84
- cmds.append(DeployCommand())
85
- else:
86
- cmds.append(SubprocessCommand([c]))
85
+ if not c.startswith('{'):
86
+ c = json.dumps({c: {}})
87
+ cmd = msh.unmarshal_obj(json.loads(c), Command)
88
+ cmds.append(cmd)
87
89
 
88
90
  #
89
91
 
@@ -103,9 +105,13 @@ def _main() -> None:
103
105
  ce = es.enter_context(injector[RemoteExecution].connect(tgt, bs)) # noqa
104
106
 
105
107
  for cmd in cmds:
106
- r = ce.try_execute(cmd)
108
+ r = ce.try_execute(
109
+ cmd,
110
+ log=log,
111
+ omit_exc_object=True,
112
+ )
107
113
 
108
- print(injector[ObjMarshalerManager].marshal_obj(r, opts=ObjMarshalOptions(raw_bytes=True)))
114
+ print(msh.marshal_obj(r, opts=ObjMarshalOptions(raw_bytes=True)))
109
115
 
110
116
 
111
117
  if __name__ == '__main__':
@@ -1,6 +1,7 @@
1
1
  # ruff: noqa: UP006 UP007
2
2
  import json
3
3
  import struct
4
+ import threading
4
5
  import typing as ta
5
6
 
6
7
  from omlish.lite.json import json_dumps_compact
@@ -25,10 +26,12 @@ class RemoteChannel:
25
26
  self._output = output
26
27
  self._msh = msh
27
28
 
29
+ self._lock = threading.RLock()
30
+
28
31
  def set_marshaler(self, msh: ObjMarshalerManager) -> None:
29
32
  self._msh = msh
30
33
 
31
- def send_obj(self, o: ta.Any, ty: ta.Any = None) -> None:
34
+ def _send_obj(self, o: ta.Any, ty: ta.Any = None) -> None:
32
35
  j = json_dumps_compact(self._msh.marshal_obj(o, ty))
33
36
  d = j.encode('utf-8')
34
37
 
@@ -36,7 +39,11 @@ class RemoteChannel:
36
39
  self._output.write(d)
37
40
  self._output.flush()
38
41
 
39
- def recv_obj(self, ty: ta.Type[T]) -> ta.Optional[T]:
42
+ def send_obj(self, o: ta.Any, ty: ta.Any = None) -> None:
43
+ with self._lock:
44
+ return self._send_obj(o, ty)
45
+
46
+ def _recv_obj(self, ty: ta.Type[T]) -> ta.Optional[T]:
40
47
  d = self._input.read(4)
41
48
  if not d:
42
49
  return None
@@ -50,3 +57,7 @@ class RemoteChannel:
50
57
 
51
58
  j = json.loads(d.decode('utf-8'))
52
59
  return self._msh.unmarshal_obj(j, ty)
60
+
61
+ def recv_obj(self, ty: ta.Type[T]) -> ta.Optional[T]:
62
+ with self._lock:
63
+ return self._recv_obj(ty)
@@ -2,6 +2,7 @@
2
2
  import contextlib
3
3
  import dataclasses as dc
4
4
  import logging
5
+ import threading
5
6
  import typing as ta
6
7
 
7
8
  from omlish.lite.cached import cached_nullary
@@ -36,6 +37,32 @@ else:
36
37
  ##
37
38
 
38
39
 
40
+ class _RemoteExecutionLogHandler(logging.Handler):
41
+ def __init__(self, fn: ta.Callable[[str], None]) -> None:
42
+ super().__init__()
43
+ self._fn = fn
44
+
45
+ def emit(self, record):
46
+ msg = self.format(record)
47
+ self._fn(msg)
48
+
49
+
50
+ @dc.dataclass(frozen=True)
51
+ class _RemoteExecutionRequest:
52
+ c: Command
53
+
54
+
55
+ @dc.dataclass(frozen=True)
56
+ class _RemoteExecutionLog:
57
+ s: str
58
+
59
+
60
+ @dc.dataclass(frozen=True)
61
+ class _RemoteExecutionResponse:
62
+ r: ta.Optional[CommandOutputOrExceptionData] = None
63
+ l: ta.Optional[_RemoteExecutionLog] = None
64
+
65
+
39
66
  def _remote_execution_main() -> None:
40
67
  rt = pyremote_bootstrap_finalize() # noqa
41
68
 
@@ -53,20 +80,44 @@ def _remote_execution_main() -> None:
53
80
 
54
81
  chan.set_marshaler(injector[ObjMarshalerManager])
55
82
 
83
+ #
84
+
85
+ log_lock = threading.RLock()
86
+ send_logs = False
87
+
88
+ def log_fn(s: str) -> None:
89
+ with log_lock:
90
+ if send_logs:
91
+ chan.send_obj(_RemoteExecutionResponse(l=_RemoteExecutionLog(s)))
92
+
93
+ log_handler = _RemoteExecutionLogHandler(log_fn)
94
+ logging.root.addHandler(log_handler)
95
+
96
+ #
97
+
56
98
  ce = injector[LocalCommandExecutor]
57
99
 
58
100
  while True:
59
- i = chan.recv_obj(Command)
60
- if i is None:
101
+ req = chan.recv_obj(_RemoteExecutionRequest)
102
+ if req is None:
61
103
  break
62
104
 
105
+ with log_lock:
106
+ send_logs = True
107
+
63
108
  r = ce.try_execute(
64
- i,
109
+ req.c,
65
110
  log=log,
66
111
  omit_exc_object=True,
67
112
  )
68
113
 
69
- chan.send_obj(r)
114
+ with log_lock:
115
+ send_logs = False
116
+
117
+ chan.send_obj(_RemoteExecutionResponse(r=CommandOutputOrExceptionData(
118
+ output=r.output,
119
+ exception=r.exception,
120
+ )))
70
121
 
71
122
 
72
123
  ##
@@ -84,12 +135,17 @@ class RemoteCommandExecutor(CommandExecutor):
84
135
  self._chan = chan
85
136
 
86
137
  def _remote_execute(self, cmd: Command) -> CommandOutputOrException:
87
- self._chan.send_obj(cmd, Command)
138
+ self._chan.send_obj(_RemoteExecutionRequest(cmd))
139
+
140
+ while True:
141
+ if (r := self._chan.recv_obj(_RemoteExecutionResponse)) is None:
142
+ raise EOFError
88
143
 
89
- if (r := self._chan.recv_obj(CommandOutputOrExceptionData)) is None:
90
- raise EOFError
144
+ if r.l is not None:
145
+ log.info(r.l.s)
91
146
 
92
- return r
147
+ if r.r is not None:
148
+ return r.r
93
149
 
94
150
  # @ta.override
95
151
  def execute(self, cmd: Command) -> Command.Output: