ominfra 0.0.0.dev181__py3-none-any.whl → 0.0.0.dev183__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/configs.py +33 -0
- ominfra/manage/deploy/apps.py +1 -1
- ominfra/manage/deploy/conf/__init__.py +0 -0
- ominfra/manage/deploy/conf/inject.py +17 -0
- ominfra/manage/deploy/{conf.py → conf/manager.py} +28 -6
- ominfra/manage/deploy/conf/specs.py +95 -0
- ominfra/manage/deploy/inject.py +9 -18
- ominfra/manage/deploy/inject_.py +13 -0
- ominfra/manage/deploy/paths/specs.py +10 -0
- ominfra/manage/deploy/specs.py +1 -59
- ominfra/manage/deploy/tags.py +2 -3
- ominfra/scripts/journald2aws.py +42 -0
- ominfra/scripts/manage.py +664 -548
- ominfra/scripts/supervisor.py +42 -0
- {ominfra-0.0.0.dev181.dist-info → ominfra-0.0.0.dev183.dist-info}/METADATA +3 -3
- {ominfra-0.0.0.dev181.dist-info → ominfra-0.0.0.dev183.dist-info}/RECORD +20 -16
- ominfra/systemd.py +0 -18
- {ominfra-0.0.0.dev181.dist-info → ominfra-0.0.0.dev183.dist-info}/LICENSE +0 -0
- {ominfra-0.0.0.dev181.dist-info → ominfra-0.0.0.dev183.dist-info}/WHEEL +0 -0
- {ominfra-0.0.0.dev181.dist-info → ominfra-0.0.0.dev183.dist-info}/entry_points.txt +0 -0
- {ominfra-0.0.0.dev181.dist-info → ominfra-0.0.0.dev183.dist-info}/top_level.txt +0 -0
ominfra/scripts/manage.py
CHANGED
@@ -129,6 +129,7 @@ AtomicPathSwapState = ta.Literal['open', 'committed', 'aborted'] # ta.TypeAlias
|
|
129
129
|
|
130
130
|
# ../configs.py
|
131
131
|
ConfigMapping = ta.Mapping[str, ta.Any]
|
132
|
+
IniConfigSectionSettingsMap = ta.Mapping[str, ta.Mapping[str, ta.Union[str, ta.Sequence[str]]]] # ta.TypeAlias
|
132
133
|
|
133
134
|
# ../../omlish/subprocesses.py
|
134
135
|
SubprocessChannelOption = ta.Literal['pipe', 'stdout', 'devnull'] # ta.TypeAlias
|
@@ -4252,6 +4253,18 @@ def build_command_name_map(crs: CommandRegistrations) -> CommandNameMap:
|
|
4252
4253
|
return CommandNameMap(dct)
|
4253
4254
|
|
4254
4255
|
|
4256
|
+
########################################
|
4257
|
+
# ../deploy/paths/specs.py
|
4258
|
+
|
4259
|
+
|
4260
|
+
def check_valid_deploy_spec_path(s: str) -> str:
|
4261
|
+
check.non_empty_str(s)
|
4262
|
+
for c in ['..', '//']:
|
4263
|
+
check.not_in(c, s)
|
4264
|
+
check.arg(not s.startswith('/'))
|
4265
|
+
return s
|
4266
|
+
|
4267
|
+
|
4255
4268
|
########################################
|
4256
4269
|
# ../remote/config.py
|
4257
4270
|
|
@@ -6225,6 +6238,17 @@ def register_type_obj_marshaler(ty: type, om: ObjMarshaler) -> None:
|
|
6225
6238
|
_REGISTERED_OBJ_MARSHALERS_BY_TYPE[ty] = om
|
6226
6239
|
|
6227
6240
|
|
6241
|
+
def register_single_field_type_obj_marshaler(fld, ty=None):
|
6242
|
+
def inner(ty): # noqa
|
6243
|
+
register_type_obj_marshaler(ty, SingleFieldObjMarshaler(ty, fld))
|
6244
|
+
return ty
|
6245
|
+
|
6246
|
+
if ty is not None:
|
6247
|
+
return inner(ty)
|
6248
|
+
else:
|
6249
|
+
return inner
|
6250
|
+
|
6251
|
+
|
6228
6252
|
##
|
6229
6253
|
|
6230
6254
|
|
@@ -6821,6 +6845,9 @@ def bind_interp_uv() -> InjectorBindings:
|
|
6821
6845
|
# ../../configs.py
|
6822
6846
|
|
6823
6847
|
|
6848
|
+
##
|
6849
|
+
|
6850
|
+
|
6824
6851
|
def parse_config_file(
|
6825
6852
|
name: str,
|
6826
6853
|
f: ta.TextIO,
|
@@ -6864,6 +6891,9 @@ def read_config_file(
|
|
6864
6891
|
return msh.unmarshal_obj(config_dct, cls)
|
6865
6892
|
|
6866
6893
|
|
6894
|
+
##
|
6895
|
+
|
6896
|
+
|
6867
6897
|
def build_config_named_children(
|
6868
6898
|
o: ta.Union[
|
6869
6899
|
ta.Sequence[ConfigMapping],
|
@@ -6902,6 +6932,30 @@ def build_config_named_children(
|
|
6902
6932
|
return lst
|
6903
6933
|
|
6904
6934
|
|
6935
|
+
##
|
6936
|
+
|
6937
|
+
|
6938
|
+
def render_ini_config(
|
6939
|
+
settings_by_section: IniConfigSectionSettingsMap,
|
6940
|
+
) -> str:
|
6941
|
+
out = io.StringIO()
|
6942
|
+
|
6943
|
+
for i, (section, settings) in enumerate(settings_by_section.items()):
|
6944
|
+
if i:
|
6945
|
+
out.write('\n')
|
6946
|
+
|
6947
|
+
out.write(f'[{section}]\n')
|
6948
|
+
|
6949
|
+
for k, v in settings.items():
|
6950
|
+
if isinstance(v, str):
|
6951
|
+
out.write(f'{k}={v}\n')
|
6952
|
+
else:
|
6953
|
+
for vv in v:
|
6954
|
+
out.write(f'{k}={vv}\n')
|
6955
|
+
|
6956
|
+
return out.getvalue()
|
6957
|
+
|
6958
|
+
|
6905
6959
|
########################################
|
6906
6960
|
# ../commands/marshal.py
|
6907
6961
|
|
@@ -7035,7 +7089,7 @@ def _register_deploy_tag(cls):
|
|
7035
7089
|
_DEPLOY_TAGS_BY_NAME[cls.tag_name] = cls
|
7036
7090
|
_DEPLOY_TAGS_BY_KWARG[cls.tag_kwarg] = cls
|
7037
7091
|
|
7038
|
-
|
7092
|
+
register_single_field_type_obj_marshaler('s', cls)
|
7039
7093
|
|
7040
7094
|
return cls
|
7041
7095
|
|
@@ -7808,6 +7862,95 @@ class LocalCommandExecutor(CommandExecutor):
|
|
7808
7862
|
return await ce.execute(cmd)
|
7809
7863
|
|
7810
7864
|
|
7865
|
+
########################################
|
7866
|
+
# ../deploy/conf/specs.py
|
7867
|
+
|
7868
|
+
|
7869
|
+
##
|
7870
|
+
|
7871
|
+
|
7872
|
+
class DeployAppConfContent(abc.ABC): # noqa
|
7873
|
+
pass
|
7874
|
+
|
7875
|
+
|
7876
|
+
#
|
7877
|
+
|
7878
|
+
|
7879
|
+
@register_single_field_type_obj_marshaler('body')
|
7880
|
+
@dc.dataclass(frozen=True)
|
7881
|
+
class RawDeployAppConfContent(DeployAppConfContent):
|
7882
|
+
body: str
|
7883
|
+
|
7884
|
+
|
7885
|
+
#
|
7886
|
+
|
7887
|
+
|
7888
|
+
@register_single_field_type_obj_marshaler('obj')
|
7889
|
+
@dc.dataclass(frozen=True)
|
7890
|
+
class JsonDeployAppConfContent(DeployAppConfContent):
|
7891
|
+
obj: ta.Any
|
7892
|
+
|
7893
|
+
|
7894
|
+
#
|
7895
|
+
|
7896
|
+
|
7897
|
+
@register_single_field_type_obj_marshaler('sections')
|
7898
|
+
@dc.dataclass(frozen=True)
|
7899
|
+
class IniDeployAppConfContent(DeployAppConfContent):
|
7900
|
+
sections: IniConfigSectionSettingsMap
|
7901
|
+
|
7902
|
+
|
7903
|
+
##
|
7904
|
+
|
7905
|
+
|
7906
|
+
@dc.dataclass(frozen=True)
|
7907
|
+
class DeployAppConfFile:
|
7908
|
+
path: str
|
7909
|
+
content: DeployAppConfContent
|
7910
|
+
|
7911
|
+
def __post_init__(self) -> None:
|
7912
|
+
check_valid_deploy_spec_path(self.path)
|
7913
|
+
|
7914
|
+
|
7915
|
+
##
|
7916
|
+
|
7917
|
+
|
7918
|
+
@dc.dataclass(frozen=True)
|
7919
|
+
class DeployAppConfLink: # noqa
|
7920
|
+
"""
|
7921
|
+
May be either:
|
7922
|
+
- @conf(.ext)* - links a single file in root of app conf dir to conf/@conf/@dst(.ext)*
|
7923
|
+
- @conf/file - links a single file in a single subdir to conf/@conf/@dst--file
|
7924
|
+
- @conf/ - links a directory in root of app conf dir to conf/@conf/@dst/
|
7925
|
+
"""
|
7926
|
+
|
7927
|
+
src: str
|
7928
|
+
|
7929
|
+
kind: ta.Literal['current_only', 'all_active'] = 'current_only'
|
7930
|
+
|
7931
|
+
def __post_init__(self) -> None:
|
7932
|
+
check_valid_deploy_spec_path(self.src)
|
7933
|
+
if '/' in self.src:
|
7934
|
+
check.equal(self.src.count('/'), 1)
|
7935
|
+
|
7936
|
+
|
7937
|
+
##
|
7938
|
+
|
7939
|
+
|
7940
|
+
@dc.dataclass(frozen=True)
|
7941
|
+
class DeployAppConfSpec:
|
7942
|
+
files: ta.Optional[ta.Sequence[DeployAppConfFile]] = None
|
7943
|
+
|
7944
|
+
links: ta.Optional[ta.Sequence[DeployAppConfLink]] = None
|
7945
|
+
|
7946
|
+
def __post_init__(self) -> None:
|
7947
|
+
if self.files:
|
7948
|
+
seen: ta.Set[str] = set()
|
7949
|
+
for f in self.files:
|
7950
|
+
check.not_in(f.path, seen)
|
7951
|
+
seen.add(f.path)
|
7952
|
+
|
7953
|
+
|
7811
7954
|
########################################
|
7812
7955
|
# ../deploy/deploy.py
|
7813
7956
|
|
@@ -8038,283 +8181,125 @@ class DeployPath:
|
|
8038
8181
|
|
8039
8182
|
|
8040
8183
|
########################################
|
8041
|
-
# ../
|
8184
|
+
# ../remote/execution.py
|
8185
|
+
"""
|
8186
|
+
TODO:
|
8187
|
+
- sequence all messages
|
8188
|
+
"""
|
8042
8189
|
|
8043
8190
|
|
8044
8191
|
##
|
8045
8192
|
|
8046
8193
|
|
8047
|
-
|
8048
|
-
|
8049
|
-
|
8050
|
-
|
8051
|
-
check.arg(not s.startswith('/'))
|
8052
|
-
return s
|
8194
|
+
class _RemoteProtocol:
|
8195
|
+
class Message(abc.ABC): # noqa
|
8196
|
+
async def send(self, chan: RemoteChannel) -> None:
|
8197
|
+
await chan.send_obj(self, _RemoteProtocol.Message)
|
8053
8198
|
|
8199
|
+
@classmethod
|
8200
|
+
async def recv(cls: ta.Type[T], chan: RemoteChannel) -> ta.Optional[T]:
|
8201
|
+
return await chan.recv_obj(cls)
|
8054
8202
|
|
8055
|
-
|
8056
|
-
@cached_nullary
|
8057
|
-
def _key_str(self) -> str:
|
8058
|
-
return hashlib.sha256(repr(self).encode('utf-8')).hexdigest()[:8]
|
8203
|
+
#
|
8059
8204
|
|
8060
|
-
|
8061
|
-
|
8062
|
-
raise NotImplementedError
|
8205
|
+
class Request(Message, abc.ABC): # noqa
|
8206
|
+
pass
|
8063
8207
|
|
8208
|
+
@dc.dataclass(frozen=True)
|
8209
|
+
class CommandRequest(Request):
|
8210
|
+
seq: int
|
8211
|
+
cmd: Command
|
8064
8212
|
|
8065
|
-
|
8213
|
+
@dc.dataclass(frozen=True)
|
8214
|
+
class PingRequest(Request):
|
8215
|
+
time: float
|
8066
8216
|
|
8217
|
+
#
|
8067
8218
|
|
8068
|
-
|
8069
|
-
|
8070
|
-
host: ta.Optional[str] = None
|
8071
|
-
username: ta.Optional[str] = None
|
8072
|
-
path: ta.Optional[str] = None
|
8219
|
+
class Response(Message, abc.ABC): # noqa
|
8220
|
+
pass
|
8073
8221
|
|
8074
|
-
|
8075
|
-
|
8076
|
-
|
8222
|
+
@dc.dataclass(frozen=True)
|
8223
|
+
class LogResponse(Response):
|
8224
|
+
s: str
|
8077
8225
|
|
8226
|
+
@dc.dataclass(frozen=True)
|
8227
|
+
class CommandResponse(Response):
|
8228
|
+
seq: int
|
8229
|
+
res: CommandOutputOrExceptionData
|
8078
8230
|
|
8079
|
-
@dc.dataclass(frozen=True)
|
8080
|
-
class
|
8081
|
-
|
8082
|
-
rev: DeployRev
|
8231
|
+
@dc.dataclass(frozen=True)
|
8232
|
+
class PingResponse(Response):
|
8233
|
+
time: float
|
8083
8234
|
|
8084
|
-
subtrees: ta.Optional[ta.Sequence[str]] = None
|
8085
8235
|
|
8086
|
-
|
8087
|
-
check.non_empty_str(self.rev)
|
8088
|
-
if self.subtrees is not None:
|
8089
|
-
for st in self.subtrees:
|
8090
|
-
check.non_empty_str(st)
|
8236
|
+
##
|
8091
8237
|
|
8092
8238
|
|
8093
|
-
|
8239
|
+
class _RemoteLogHandler(logging.Handler):
|
8240
|
+
def __init__(
|
8241
|
+
self,
|
8242
|
+
chan: RemoteChannel,
|
8243
|
+
loop: ta.Any = None,
|
8244
|
+
) -> None:
|
8245
|
+
super().__init__()
|
8094
8246
|
|
8247
|
+
self._chan = chan
|
8248
|
+
self._loop = loop
|
8095
8249
|
|
8096
|
-
|
8097
|
-
|
8098
|
-
interp: ta.Optional[str] = None
|
8250
|
+
def emit(self, record):
|
8251
|
+
msg = self.format(record)
|
8099
8252
|
|
8100
|
-
|
8101
|
-
|
8253
|
+
async def inner():
|
8254
|
+
await _RemoteProtocol.LogResponse(msg).send(self._chan)
|
8102
8255
|
|
8103
|
-
|
8256
|
+
loop = self._loop
|
8257
|
+
if loop is None:
|
8258
|
+
loop = asyncio.get_running_loop()
|
8259
|
+
if loop is not None:
|
8260
|
+
asyncio.run_coroutine_threadsafe(inner(), loop)
|
8104
8261
|
|
8105
8262
|
|
8106
8263
|
##
|
8107
8264
|
|
8108
8265
|
|
8109
|
-
|
8110
|
-
|
8111
|
-
path: str
|
8112
|
-
body: str
|
8266
|
+
class _RemoteCommandHandler:
|
8267
|
+
DEFAULT_PING_INTERVAL_S: float = 3.
|
8113
8268
|
|
8114
|
-
def
|
8115
|
-
|
8269
|
+
def __init__(
|
8270
|
+
self,
|
8271
|
+
chan: RemoteChannel,
|
8272
|
+
executor: CommandExecutor,
|
8273
|
+
*,
|
8274
|
+
stop: ta.Optional[asyncio.Event] = None,
|
8275
|
+
ping_interval_s: float = DEFAULT_PING_INTERVAL_S,
|
8276
|
+
) -> None:
|
8277
|
+
super().__init__()
|
8116
8278
|
|
8279
|
+
self._chan = chan
|
8280
|
+
self._executor = executor
|
8281
|
+
self._stop = stop if stop is not None else asyncio.Event()
|
8282
|
+
self._ping_interval_s = ping_interval_s
|
8117
8283
|
|
8118
|
-
|
8284
|
+
self._cmds_by_seq: ta.Dict[int, _RemoteCommandHandler._Command] = {}
|
8119
8285
|
|
8286
|
+
self._last_ping_send: float = 0.
|
8287
|
+
self._ping_in_flight: bool = False
|
8288
|
+
self._last_ping_recv: ta.Optional[float] = None
|
8120
8289
|
|
8121
|
-
|
8122
|
-
|
8123
|
-
"""
|
8124
|
-
May be either:
|
8125
|
-
- @conf(.ext)* - links a single file in root of app conf dir to conf/@conf/@dst(.ext)*
|
8126
|
-
- @conf/file - links a single file in a single subdir to conf/@conf/@dst--file
|
8127
|
-
- @conf/ - links a directory in root of app conf dir to conf/@conf/@dst/
|
8128
|
-
"""
|
8290
|
+
def stop(self) -> None:
|
8291
|
+
self._stop.set()
|
8129
8292
|
|
8130
|
-
|
8293
|
+
@dc.dataclass(frozen=True)
|
8294
|
+
class _Command:
|
8295
|
+
req: _RemoteProtocol.CommandRequest
|
8296
|
+
fut: asyncio.Future
|
8131
8297
|
|
8132
|
-
|
8298
|
+
async def run(self) -> None:
|
8299
|
+
log.debug('_RemoteCommandHandler loop start: %r', self)
|
8133
8300
|
|
8134
|
-
|
8135
|
-
|
8136
|
-
if '/' in self.src:
|
8137
|
-
check.equal(self.src.count('/'), 1)
|
8138
|
-
|
8139
|
-
|
8140
|
-
#
|
8141
|
-
|
8142
|
-
|
8143
|
-
@dc.dataclass(frozen=True)
|
8144
|
-
class DeployAppConfSpec:
|
8145
|
-
files: ta.Optional[ta.Sequence[DeployAppConfFile]] = None
|
8146
|
-
|
8147
|
-
links: ta.Optional[ta.Sequence[DeployAppConfLink]] = None
|
8148
|
-
|
8149
|
-
def __post_init__(self) -> None:
|
8150
|
-
if self.files:
|
8151
|
-
seen: ta.Set[str] = set()
|
8152
|
-
for f in self.files:
|
8153
|
-
check.not_in(f.path, seen)
|
8154
|
-
seen.add(f.path)
|
8155
|
-
|
8156
|
-
|
8157
|
-
##
|
8158
|
-
|
8159
|
-
|
8160
|
-
@dc.dataclass(frozen=True)
|
8161
|
-
class DeployAppSpec(DeploySpecKeyed[DeployAppKey]):
|
8162
|
-
app: DeployApp
|
8163
|
-
|
8164
|
-
git: DeployGitSpec
|
8165
|
-
|
8166
|
-
venv: ta.Optional[DeployVenvSpec] = None
|
8167
|
-
|
8168
|
-
conf: ta.Optional[DeployAppConfSpec] = None
|
8169
|
-
|
8170
|
-
# @ta.override
|
8171
|
-
def key(self) -> DeployAppKey:
|
8172
|
-
return DeployAppKey(self._key_str())
|
8173
|
-
|
8174
|
-
|
8175
|
-
##
|
8176
|
-
|
8177
|
-
|
8178
|
-
@dc.dataclass(frozen=True)
|
8179
|
-
class DeploySpec(DeploySpecKeyed[DeployKey]):
|
8180
|
-
home: DeployHome
|
8181
|
-
|
8182
|
-
apps: ta.Sequence[DeployAppSpec]
|
8183
|
-
|
8184
|
-
def __post_init__(self) -> None:
|
8185
|
-
check.non_empty_str(self.home)
|
8186
|
-
|
8187
|
-
seen: ta.Set[DeployApp] = set()
|
8188
|
-
for a in self.apps:
|
8189
|
-
if a.app in seen:
|
8190
|
-
raise KeyError(a.app)
|
8191
|
-
seen.add(a.app)
|
8192
|
-
|
8193
|
-
# @ta.override
|
8194
|
-
def key(self) -> DeployKey:
|
8195
|
-
return DeployKey(self._key_str())
|
8196
|
-
|
8197
|
-
|
8198
|
-
########################################
|
8199
|
-
# ../remote/execution.py
|
8200
|
-
"""
|
8201
|
-
TODO:
|
8202
|
-
- sequence all messages
|
8203
|
-
"""
|
8204
|
-
|
8205
|
-
|
8206
|
-
##
|
8207
|
-
|
8208
|
-
|
8209
|
-
class _RemoteProtocol:
|
8210
|
-
class Message(abc.ABC): # noqa
|
8211
|
-
async def send(self, chan: RemoteChannel) -> None:
|
8212
|
-
await chan.send_obj(self, _RemoteProtocol.Message)
|
8213
|
-
|
8214
|
-
@classmethod
|
8215
|
-
async def recv(cls: ta.Type[T], chan: RemoteChannel) -> ta.Optional[T]:
|
8216
|
-
return await chan.recv_obj(cls)
|
8217
|
-
|
8218
|
-
#
|
8219
|
-
|
8220
|
-
class Request(Message, abc.ABC): # noqa
|
8221
|
-
pass
|
8222
|
-
|
8223
|
-
@dc.dataclass(frozen=True)
|
8224
|
-
class CommandRequest(Request):
|
8225
|
-
seq: int
|
8226
|
-
cmd: Command
|
8227
|
-
|
8228
|
-
@dc.dataclass(frozen=True)
|
8229
|
-
class PingRequest(Request):
|
8230
|
-
time: float
|
8231
|
-
|
8232
|
-
#
|
8233
|
-
|
8234
|
-
class Response(Message, abc.ABC): # noqa
|
8235
|
-
pass
|
8236
|
-
|
8237
|
-
@dc.dataclass(frozen=True)
|
8238
|
-
class LogResponse(Response):
|
8239
|
-
s: str
|
8240
|
-
|
8241
|
-
@dc.dataclass(frozen=True)
|
8242
|
-
class CommandResponse(Response):
|
8243
|
-
seq: int
|
8244
|
-
res: CommandOutputOrExceptionData
|
8245
|
-
|
8246
|
-
@dc.dataclass(frozen=True)
|
8247
|
-
class PingResponse(Response):
|
8248
|
-
time: float
|
8249
|
-
|
8250
|
-
|
8251
|
-
##
|
8252
|
-
|
8253
|
-
|
8254
|
-
class _RemoteLogHandler(logging.Handler):
|
8255
|
-
def __init__(
|
8256
|
-
self,
|
8257
|
-
chan: RemoteChannel,
|
8258
|
-
loop: ta.Any = None,
|
8259
|
-
) -> None:
|
8260
|
-
super().__init__()
|
8261
|
-
|
8262
|
-
self._chan = chan
|
8263
|
-
self._loop = loop
|
8264
|
-
|
8265
|
-
def emit(self, record):
|
8266
|
-
msg = self.format(record)
|
8267
|
-
|
8268
|
-
async def inner():
|
8269
|
-
await _RemoteProtocol.LogResponse(msg).send(self._chan)
|
8270
|
-
|
8271
|
-
loop = self._loop
|
8272
|
-
if loop is None:
|
8273
|
-
loop = asyncio.get_running_loop()
|
8274
|
-
if loop is not None:
|
8275
|
-
asyncio.run_coroutine_threadsafe(inner(), loop)
|
8276
|
-
|
8277
|
-
|
8278
|
-
##
|
8279
|
-
|
8280
|
-
|
8281
|
-
class _RemoteCommandHandler:
|
8282
|
-
DEFAULT_PING_INTERVAL_S: float = 3.
|
8283
|
-
|
8284
|
-
def __init__(
|
8285
|
-
self,
|
8286
|
-
chan: RemoteChannel,
|
8287
|
-
executor: CommandExecutor,
|
8288
|
-
*,
|
8289
|
-
stop: ta.Optional[asyncio.Event] = None,
|
8290
|
-
ping_interval_s: float = DEFAULT_PING_INTERVAL_S,
|
8291
|
-
) -> None:
|
8292
|
-
super().__init__()
|
8293
|
-
|
8294
|
-
self._chan = chan
|
8295
|
-
self._executor = executor
|
8296
|
-
self._stop = stop if stop is not None else asyncio.Event()
|
8297
|
-
self._ping_interval_s = ping_interval_s
|
8298
|
-
|
8299
|
-
self._cmds_by_seq: ta.Dict[int, _RemoteCommandHandler._Command] = {}
|
8300
|
-
|
8301
|
-
self._last_ping_send: float = 0.
|
8302
|
-
self._ping_in_flight: bool = False
|
8303
|
-
self._last_ping_recv: ta.Optional[float] = None
|
8304
|
-
|
8305
|
-
def stop(self) -> None:
|
8306
|
-
self._stop.set()
|
8307
|
-
|
8308
|
-
@dc.dataclass(frozen=True)
|
8309
|
-
class _Command:
|
8310
|
-
req: _RemoteProtocol.CommandRequest
|
8311
|
-
fut: asyncio.Future
|
8312
|
-
|
8313
|
-
async def run(self) -> None:
|
8314
|
-
log.debug('_RemoteCommandHandler loop start: %r', self)
|
8315
|
-
|
8316
|
-
stop_task = asyncio.create_task(self._stop.wait())
|
8317
|
-
recv_task: ta.Optional[asyncio.Task] = None
|
8301
|
+
stop_task = asyncio.create_task(self._stop.wait())
|
8302
|
+
recv_task: ta.Optional[asyncio.Task] = None
|
8318
8303
|
|
8319
8304
|
while not self._stop.is_set():
|
8320
8305
|
if recv_task is None:
|
@@ -8914,6 +8899,88 @@ class InterpInspector:
|
|
8914
8899
|
return ret
|
8915
8900
|
|
8916
8901
|
|
8902
|
+
########################################
|
8903
|
+
# ../../../omdev/interp/pyenv/pyenv.py
|
8904
|
+
"""
|
8905
|
+
TODO:
|
8906
|
+
- custom tags
|
8907
|
+
- 'aliases'
|
8908
|
+
- https://github.com/pyenv/pyenv/pull/2966
|
8909
|
+
- https://github.com/pyenv/pyenv/issues/218 (lol)
|
8910
|
+
- probably need custom (temp?) definition file
|
8911
|
+
- *or* python-build directly just into the versions dir?
|
8912
|
+
- optionally install / upgrade pyenv itself
|
8913
|
+
- new vers dont need these custom mac opts, only run on old vers
|
8914
|
+
"""
|
8915
|
+
|
8916
|
+
|
8917
|
+
class Pyenv:
|
8918
|
+
def __init__(
|
8919
|
+
self,
|
8920
|
+
*,
|
8921
|
+
root: ta.Optional[str] = None,
|
8922
|
+
) -> None:
|
8923
|
+
if root is not None and not (isinstance(root, str) and root):
|
8924
|
+
raise ValueError(f'pyenv_root: {root!r}')
|
8925
|
+
|
8926
|
+
super().__init__()
|
8927
|
+
|
8928
|
+
self._root_kw = root
|
8929
|
+
|
8930
|
+
@async_cached_nullary
|
8931
|
+
async def root(self) -> ta.Optional[str]:
|
8932
|
+
if self._root_kw is not None:
|
8933
|
+
return self._root_kw
|
8934
|
+
|
8935
|
+
if shutil.which('pyenv'):
|
8936
|
+
return await asyncio_subprocesses.check_output_str('pyenv', 'root')
|
8937
|
+
|
8938
|
+
d = os.path.expanduser('~/.pyenv')
|
8939
|
+
if os.path.isdir(d) and os.path.isfile(os.path.join(d, 'bin', 'pyenv')):
|
8940
|
+
return d
|
8941
|
+
|
8942
|
+
return None
|
8943
|
+
|
8944
|
+
@async_cached_nullary
|
8945
|
+
async def exe(self) -> str:
|
8946
|
+
return os.path.join(check.not_none(await self.root()), 'bin', 'pyenv')
|
8947
|
+
|
8948
|
+
async def version_exes(self) -> ta.List[ta.Tuple[str, str]]:
|
8949
|
+
if (root := await self.root()) is None:
|
8950
|
+
return []
|
8951
|
+
ret = []
|
8952
|
+
vp = os.path.join(root, 'versions')
|
8953
|
+
if os.path.isdir(vp):
|
8954
|
+
for dn in os.listdir(vp):
|
8955
|
+
ep = os.path.join(vp, dn, 'bin', 'python')
|
8956
|
+
if not os.path.isfile(ep):
|
8957
|
+
continue
|
8958
|
+
ret.append((dn, ep))
|
8959
|
+
return ret
|
8960
|
+
|
8961
|
+
async def installable_versions(self) -> ta.List[str]:
|
8962
|
+
if await self.root() is None:
|
8963
|
+
return []
|
8964
|
+
ret = []
|
8965
|
+
s = await asyncio_subprocesses.check_output_str(await self.exe(), 'install', '--list')
|
8966
|
+
for l in s.splitlines():
|
8967
|
+
if not l.startswith(' '):
|
8968
|
+
continue
|
8969
|
+
l = l.strip()
|
8970
|
+
if not l:
|
8971
|
+
continue
|
8972
|
+
ret.append(l)
|
8973
|
+
return ret
|
8974
|
+
|
8975
|
+
async def update(self) -> bool:
|
8976
|
+
if (root := await self.root()) is None:
|
8977
|
+
return False
|
8978
|
+
if not os.path.isdir(os.path.join(root, '.git')):
|
8979
|
+
return False
|
8980
|
+
await asyncio_subprocesses.check_call('git', 'pull', cwd=root)
|
8981
|
+
return True
|
8982
|
+
|
8983
|
+
|
8917
8984
|
########################################
|
8918
8985
|
# ../../../omdev/interp/resolvers.py
|
8919
8986
|
|
@@ -9073,7 +9140,7 @@ class SubprocessCommandExecutor(CommandExecutor[SubprocessCommand, SubprocessCom
|
|
9073
9140
|
|
9074
9141
|
|
9075
9142
|
########################################
|
9076
|
-
# ../deploy/conf.py
|
9143
|
+
# ../deploy/conf/manager.py
|
9077
9144
|
"""
|
9078
9145
|
TODO:
|
9079
9146
|
- @conf DeployPathPlaceholder? :|
|
@@ -9094,6 +9161,19 @@ TODO:
|
|
9094
9161
|
|
9095
9162
|
|
9096
9163
|
class DeployConfManager:
|
9164
|
+
def _render_app_conf_content(self, ac: DeployAppConfContent) -> str:
|
9165
|
+
if isinstance(ac, RawDeployAppConfContent):
|
9166
|
+
return ac.body
|
9167
|
+
|
9168
|
+
elif isinstance(ac, JsonDeployAppConfContent):
|
9169
|
+
return strip_with_newline(json_dumps_pretty(ac.obj))
|
9170
|
+
|
9171
|
+
elif isinstance(ac, IniDeployAppConfContent):
|
9172
|
+
return strip_with_newline(render_ini_config(ac.sections))
|
9173
|
+
|
9174
|
+
else:
|
9175
|
+
raise TypeError(ac)
|
9176
|
+
|
9097
9177
|
async def _write_app_conf_file(
|
9098
9178
|
self,
|
9099
9179
|
acf: DeployAppConfFile,
|
@@ -9102,10 +9182,12 @@ class DeployConfManager:
|
|
9102
9182
|
conf_file = os.path.join(app_conf_dir, acf.path)
|
9103
9183
|
check.arg(is_path_in_dir(app_conf_dir, conf_file))
|
9104
9184
|
|
9185
|
+
body = self._render_app_conf_content(acf.content)
|
9186
|
+
|
9105
9187
|
os.makedirs(os.path.dirname(conf_file), exist_ok=True)
|
9106
9188
|
|
9107
9189
|
with open(conf_file, 'w') as f: # noqa
|
9108
|
-
f.write(
|
9190
|
+
f.write(body)
|
9109
9191
|
|
9110
9192
|
#
|
9111
9193
|
|
@@ -9252,33 +9334,132 @@ class DeployPathOwner(abc.ABC):
|
|
9252
9334
|
raise NotImplementedError
|
9253
9335
|
|
9254
9336
|
|
9255
|
-
DeployPathOwners = ta.NewType('DeployPathOwners', ta.Sequence[DeployPathOwner])
|
9337
|
+
DeployPathOwners = ta.NewType('DeployPathOwners', ta.Sequence[DeployPathOwner])
|
9338
|
+
|
9339
|
+
|
9340
|
+
class SingleDirDeployPathOwner(DeployPathOwner, abc.ABC):
|
9341
|
+
def __init__(
|
9342
|
+
self,
|
9343
|
+
*args: ta.Any,
|
9344
|
+
owned_dir: str,
|
9345
|
+
**kwargs: ta.Any,
|
9346
|
+
) -> None:
|
9347
|
+
super().__init__(*args, **kwargs)
|
9348
|
+
|
9349
|
+
check.not_in('/', owned_dir)
|
9350
|
+
self._owned_dir: str = check.non_empty_str(owned_dir)
|
9351
|
+
|
9352
|
+
self._owned_deploy_paths = frozenset([DeployPath.parse(self._owned_dir + '/')])
|
9353
|
+
|
9354
|
+
def _dir(self, home: DeployHome) -> str:
|
9355
|
+
return os.path.join(check.non_empty_str(home), self._owned_dir)
|
9356
|
+
|
9357
|
+
def _make_dir(self, home: DeployHome) -> str:
|
9358
|
+
if not os.path.isdir(d := self._dir(home)):
|
9359
|
+
os.makedirs(d, exist_ok=True)
|
9360
|
+
return d
|
9361
|
+
|
9362
|
+
def get_owned_deploy_paths(self) -> ta.AbstractSet[DeployPath]:
|
9363
|
+
return self._owned_deploy_paths
|
9364
|
+
|
9365
|
+
|
9366
|
+
########################################
|
9367
|
+
# ../deploy/specs.py
|
9368
|
+
|
9369
|
+
|
9370
|
+
##
|
9371
|
+
|
9372
|
+
|
9373
|
+
class DeploySpecKeyed(ta.Generic[KeyDeployTagT]):
|
9374
|
+
@cached_nullary
|
9375
|
+
def _key_str(self) -> str:
|
9376
|
+
return hashlib.sha256(repr(self).encode('utf-8')).hexdigest()[:8]
|
9377
|
+
|
9378
|
+
@abc.abstractmethod
|
9379
|
+
def key(self) -> KeyDeployTagT:
|
9380
|
+
raise NotImplementedError
|
9381
|
+
|
9382
|
+
|
9383
|
+
##
|
9384
|
+
|
9385
|
+
|
9386
|
+
@dc.dataclass(frozen=True)
|
9387
|
+
class DeployGitRepo:
|
9388
|
+
host: ta.Optional[str] = None
|
9389
|
+
username: ta.Optional[str] = None
|
9390
|
+
path: ta.Optional[str] = None
|
9391
|
+
|
9392
|
+
def __post_init__(self) -> None:
|
9393
|
+
check.not_in('..', check.non_empty_str(self.host))
|
9394
|
+
check.not_in('.', check.non_empty_str(self.path))
|
9395
|
+
|
9396
|
+
|
9397
|
+
@dc.dataclass(frozen=True)
|
9398
|
+
class DeployGitSpec:
|
9399
|
+
repo: DeployGitRepo
|
9400
|
+
rev: DeployRev
|
9401
|
+
|
9402
|
+
subtrees: ta.Optional[ta.Sequence[str]] = None
|
9403
|
+
|
9404
|
+
def __post_init__(self) -> None:
|
9405
|
+
check.non_empty_str(self.rev)
|
9406
|
+
if self.subtrees is not None:
|
9407
|
+
for st in self.subtrees:
|
9408
|
+
check.non_empty_str(st)
|
9409
|
+
|
9410
|
+
|
9411
|
+
##
|
9412
|
+
|
9413
|
+
|
9414
|
+
@dc.dataclass(frozen=True)
|
9415
|
+
class DeployVenvSpec:
|
9416
|
+
interp: ta.Optional[str] = None
|
9417
|
+
|
9418
|
+
requirements_files: ta.Optional[ta.Sequence[str]] = None
|
9419
|
+
extra_dependencies: ta.Optional[ta.Sequence[str]] = None
|
9420
|
+
|
9421
|
+
use_uv: bool = False
|
9422
|
+
|
9423
|
+
|
9424
|
+
##
|
9425
|
+
|
9426
|
+
|
9427
|
+
@dc.dataclass(frozen=True)
|
9428
|
+
class DeployAppSpec(DeploySpecKeyed[DeployAppKey]):
|
9429
|
+
app: DeployApp
|
9430
|
+
|
9431
|
+
git: DeployGitSpec
|
9432
|
+
|
9433
|
+
venv: ta.Optional[DeployVenvSpec] = None
|
9434
|
+
|
9435
|
+
conf: ta.Optional[DeployAppConfSpec] = None
|
9436
|
+
|
9437
|
+
# @ta.override
|
9438
|
+
def key(self) -> DeployAppKey:
|
9439
|
+
return DeployAppKey(self._key_str())
|
9440
|
+
|
9256
9441
|
|
9442
|
+
##
|
9257
9443
|
|
9258
|
-
class SingleDirDeployPathOwner(DeployPathOwner, abc.ABC):
|
9259
|
-
def __init__(
|
9260
|
-
self,
|
9261
|
-
*args: ta.Any,
|
9262
|
-
owned_dir: str,
|
9263
|
-
**kwargs: ta.Any,
|
9264
|
-
) -> None:
|
9265
|
-
super().__init__(*args, **kwargs)
|
9266
9444
|
|
9267
|
-
|
9268
|
-
|
9445
|
+
@dc.dataclass(frozen=True)
|
9446
|
+
class DeploySpec(DeploySpecKeyed[DeployKey]):
|
9447
|
+
home: DeployHome
|
9269
9448
|
|
9270
|
-
|
9449
|
+
apps: ta.Sequence[DeployAppSpec]
|
9271
9450
|
|
9272
|
-
def
|
9273
|
-
|
9451
|
+
def __post_init__(self) -> None:
|
9452
|
+
check.non_empty_str(self.home)
|
9274
9453
|
|
9275
|
-
|
9276
|
-
|
9277
|
-
|
9278
|
-
|
9454
|
+
seen: ta.Set[DeployApp] = set()
|
9455
|
+
for a in self.apps:
|
9456
|
+
if a.app in seen:
|
9457
|
+
raise KeyError(a.app)
|
9458
|
+
seen.add(a.app)
|
9279
9459
|
|
9280
|
-
|
9281
|
-
|
9460
|
+
# @ta.override
|
9461
|
+
def key(self) -> DeployKey:
|
9462
|
+
return DeployKey(self._key_str())
|
9282
9463
|
|
9283
9464
|
|
9284
9465
|
########################################
|
@@ -9802,88 +9983,7 @@ class SystemInterpProvider(InterpProvider):
|
|
9802
9983
|
|
9803
9984
|
|
9804
9985
|
########################################
|
9805
|
-
# ../../../omdev/interp/pyenv/
|
9806
|
-
"""
|
9807
|
-
TODO:
|
9808
|
-
- custom tags
|
9809
|
-
- 'aliases'
|
9810
|
-
- https://github.com/pyenv/pyenv/pull/2966
|
9811
|
-
- https://github.com/pyenv/pyenv/issues/218 (lol)
|
9812
|
-
- probably need custom (temp?) definition file
|
9813
|
-
- *or* python-build directly just into the versions dir?
|
9814
|
-
- optionally install / upgrade pyenv itself
|
9815
|
-
- new vers dont need these custom mac opts, only run on old vers
|
9816
|
-
"""
|
9817
|
-
|
9818
|
-
|
9819
|
-
##
|
9820
|
-
|
9821
|
-
|
9822
|
-
class Pyenv:
|
9823
|
-
def __init__(
|
9824
|
-
self,
|
9825
|
-
*,
|
9826
|
-
root: ta.Optional[str] = None,
|
9827
|
-
) -> None:
|
9828
|
-
if root is not None and not (isinstance(root, str) and root):
|
9829
|
-
raise ValueError(f'pyenv_root: {root!r}')
|
9830
|
-
|
9831
|
-
super().__init__()
|
9832
|
-
|
9833
|
-
self._root_kw = root
|
9834
|
-
|
9835
|
-
@async_cached_nullary
|
9836
|
-
async def root(self) -> ta.Optional[str]:
|
9837
|
-
if self._root_kw is not None:
|
9838
|
-
return self._root_kw
|
9839
|
-
|
9840
|
-
if shutil.which('pyenv'):
|
9841
|
-
return await asyncio_subprocesses.check_output_str('pyenv', 'root')
|
9842
|
-
|
9843
|
-
d = os.path.expanduser('~/.pyenv')
|
9844
|
-
if os.path.isdir(d) and os.path.isfile(os.path.join(d, 'bin', 'pyenv')):
|
9845
|
-
return d
|
9846
|
-
|
9847
|
-
return None
|
9848
|
-
|
9849
|
-
@async_cached_nullary
|
9850
|
-
async def exe(self) -> str:
|
9851
|
-
return os.path.join(check.not_none(await self.root()), 'bin', 'pyenv')
|
9852
|
-
|
9853
|
-
async def version_exes(self) -> ta.List[ta.Tuple[str, str]]:
|
9854
|
-
if (root := await self.root()) is None:
|
9855
|
-
return []
|
9856
|
-
ret = []
|
9857
|
-
vp = os.path.join(root, 'versions')
|
9858
|
-
if os.path.isdir(vp):
|
9859
|
-
for dn in os.listdir(vp):
|
9860
|
-
ep = os.path.join(vp, dn, 'bin', 'python')
|
9861
|
-
if not os.path.isfile(ep):
|
9862
|
-
continue
|
9863
|
-
ret.append((dn, ep))
|
9864
|
-
return ret
|
9865
|
-
|
9866
|
-
async def installable_versions(self) -> ta.List[str]:
|
9867
|
-
if await self.root() is None:
|
9868
|
-
return []
|
9869
|
-
ret = []
|
9870
|
-
s = await asyncio_subprocesses.check_output_str(await self.exe(), 'install', '--list')
|
9871
|
-
for l in s.splitlines():
|
9872
|
-
if not l.startswith(' '):
|
9873
|
-
continue
|
9874
|
-
l = l.strip()
|
9875
|
-
if not l:
|
9876
|
-
continue
|
9877
|
-
ret.append(l)
|
9878
|
-
return ret
|
9879
|
-
|
9880
|
-
async def update(self) -> bool:
|
9881
|
-
if (root := await self.root()) is None:
|
9882
|
-
return False
|
9883
|
-
if not os.path.isdir(os.path.join(root, '.git')):
|
9884
|
-
return False
|
9885
|
-
await asyncio_subprocesses.check_call('git', 'pull', cwd=root)
|
9886
|
-
return True
|
9986
|
+
# ../../../omdev/interp/pyenv/install.py
|
9887
9987
|
|
9888
9988
|
|
9889
9989
|
##
|
@@ -10094,161 +10194,31 @@ class PyenvVersionInstaller:
|
|
10094
10194
|
conf_args = [
|
10095
10195
|
*opts.opts,
|
10096
10196
|
self._version,
|
10097
|
-
]
|
10098
|
-
|
10099
|
-
full_args: ta.List[str]
|
10100
|
-
if self._given_install_name is not None:
|
10101
|
-
full_args = [
|
10102
|
-
os.path.join(check.not_none(await self._pyenv.root()), 'plugins', 'python-build', 'bin', 'python-build'), # noqa
|
10103
|
-
*conf_args,
|
10104
|
-
await self.install_dir(),
|
10105
|
-
]
|
10106
|
-
else:
|
10107
|
-
full_args = [
|
10108
|
-
await self._pyenv.exe(),
|
10109
|
-
'install',
|
10110
|
-
*conf_args,
|
10111
|
-
]
|
10112
|
-
|
10113
|
-
await asyncio_subprocesses.check_call(
|
10114
|
-
*full_args,
|
10115
|
-
env=env,
|
10116
|
-
)
|
10117
|
-
|
10118
|
-
exe = os.path.join(await self.install_dir(), 'bin', 'python')
|
10119
|
-
if not os.path.isfile(exe):
|
10120
|
-
raise RuntimeError(f'Interpreter not found: {exe}')
|
10121
|
-
return exe
|
10122
|
-
|
10123
|
-
|
10124
|
-
##
|
10125
|
-
|
10126
|
-
|
10127
|
-
class PyenvInterpProvider(InterpProvider):
|
10128
|
-
@dc.dataclass(frozen=True)
|
10129
|
-
class Options:
|
10130
|
-
inspect: bool = False
|
10131
|
-
|
10132
|
-
try_update: bool = False
|
10133
|
-
|
10134
|
-
def __init__(
|
10135
|
-
self,
|
10136
|
-
options: Options = Options(),
|
10137
|
-
*,
|
10138
|
-
pyenv: Pyenv,
|
10139
|
-
inspector: InterpInspector,
|
10140
|
-
log: ta.Optional[logging.Logger] = None,
|
10141
|
-
) -> None:
|
10142
|
-
super().__init__()
|
10143
|
-
|
10144
|
-
self._options = options
|
10145
|
-
|
10146
|
-
self._pyenv = pyenv
|
10147
|
-
self._inspector = inspector
|
10148
|
-
self._log = log
|
10149
|
-
|
10150
|
-
#
|
10151
|
-
|
10152
|
-
@staticmethod
|
10153
|
-
def guess_version(s: str) -> ta.Optional[InterpVersion]:
|
10154
|
-
def strip_sfx(s: str, sfx: str) -> ta.Tuple[str, bool]:
|
10155
|
-
if s.endswith(sfx):
|
10156
|
-
return s[:-len(sfx)], True
|
10157
|
-
return s, False
|
10158
|
-
ok = {}
|
10159
|
-
s, ok['debug'] = strip_sfx(s, '-debug')
|
10160
|
-
s, ok['threaded'] = strip_sfx(s, 't')
|
10161
|
-
try:
|
10162
|
-
v = Version(s)
|
10163
|
-
except InvalidVersion:
|
10164
|
-
return None
|
10165
|
-
return InterpVersion(v, InterpOpts(**ok))
|
10166
|
-
|
10167
|
-
class Installed(ta.NamedTuple):
|
10168
|
-
name: str
|
10169
|
-
exe: str
|
10170
|
-
version: InterpVersion
|
10171
|
-
|
10172
|
-
async def _make_installed(self, vn: str, ep: str) -> ta.Optional[Installed]:
|
10173
|
-
iv: ta.Optional[InterpVersion]
|
10174
|
-
if self._options.inspect:
|
10175
|
-
try:
|
10176
|
-
iv = check.not_none(await self._inspector.inspect(ep)).iv
|
10177
|
-
except Exception as e: # noqa
|
10178
|
-
return None
|
10179
|
-
else:
|
10180
|
-
iv = self.guess_version(vn)
|
10181
|
-
if iv is None:
|
10182
|
-
return None
|
10183
|
-
return PyenvInterpProvider.Installed(
|
10184
|
-
name=vn,
|
10185
|
-
exe=ep,
|
10186
|
-
version=iv,
|
10187
|
-
)
|
10188
|
-
|
10189
|
-
async def installed(self) -> ta.Sequence[Installed]:
|
10190
|
-
ret: ta.List[PyenvInterpProvider.Installed] = []
|
10191
|
-
for vn, ep in await self._pyenv.version_exes():
|
10192
|
-
if (i := await self._make_installed(vn, ep)) is None:
|
10193
|
-
if self._log is not None:
|
10194
|
-
self._log.debug('Invalid pyenv version: %s', vn)
|
10195
|
-
continue
|
10196
|
-
ret.append(i)
|
10197
|
-
return ret
|
10198
|
-
|
10199
|
-
#
|
10200
|
-
|
10201
|
-
async def get_installed_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
|
10202
|
-
return [i.version for i in await self.installed()]
|
10203
|
-
|
10204
|
-
async def get_installed_version(self, version: InterpVersion) -> Interp:
|
10205
|
-
for i in await self.installed():
|
10206
|
-
if i.version == version:
|
10207
|
-
return Interp(
|
10208
|
-
exe=i.exe,
|
10209
|
-
version=i.version,
|
10210
|
-
)
|
10211
|
-
raise KeyError(version)
|
10212
|
-
|
10213
|
-
#
|
10214
|
-
|
10215
|
-
async def _get_installable_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
|
10216
|
-
lst = []
|
10217
|
-
|
10218
|
-
for vs in await self._pyenv.installable_versions():
|
10219
|
-
if (iv := self.guess_version(vs)) is None:
|
10220
|
-
continue
|
10221
|
-
if iv.opts.debug:
|
10222
|
-
raise Exception('Pyenv installable versions not expected to have debug suffix')
|
10223
|
-
for d in [False, True]:
|
10224
|
-
lst.append(dc.replace(iv, opts=dc.replace(iv.opts, debug=d)))
|
10225
|
-
|
10226
|
-
return lst
|
10227
|
-
|
10228
|
-
async def get_installable_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
|
10229
|
-
lst = await self._get_installable_versions(spec)
|
10230
|
-
|
10231
|
-
if self._options.try_update and not any(v in spec for v in lst):
|
10232
|
-
if self._pyenv.update():
|
10233
|
-
lst = await self._get_installable_versions(spec)
|
10234
|
-
|
10235
|
-
return lst
|
10197
|
+
]
|
10236
10198
|
|
10237
|
-
|
10238
|
-
|
10239
|
-
|
10240
|
-
|
10241
|
-
|
10242
|
-
|
10199
|
+
full_args: ta.List[str]
|
10200
|
+
if self._given_install_name is not None:
|
10201
|
+
full_args = [
|
10202
|
+
os.path.join(check.not_none(await self._pyenv.root()), 'plugins', 'python-build', 'bin', 'python-build'), # noqa
|
10203
|
+
*conf_args,
|
10204
|
+
await self.install_dir(),
|
10205
|
+
]
|
10206
|
+
else:
|
10207
|
+
full_args = [
|
10208
|
+
await self._pyenv.exe(),
|
10209
|
+
'install',
|
10210
|
+
*conf_args,
|
10211
|
+
]
|
10243
10212
|
|
10244
|
-
|
10245
|
-
|
10246
|
-
|
10247
|
-
pyenv=self._pyenv,
|
10213
|
+
await asyncio_subprocesses.check_call(
|
10214
|
+
*full_args,
|
10215
|
+
env=env,
|
10248
10216
|
)
|
10249
10217
|
|
10250
|
-
exe = await
|
10251
|
-
|
10218
|
+
exe = os.path.join(await self.install_dir(), 'bin', 'python')
|
10219
|
+
if not os.path.isfile(exe):
|
10220
|
+
raise RuntimeError(f'Interpreter not found: {exe}')
|
10221
|
+
return exe
|
10252
10222
|
|
10253
10223
|
|
10254
10224
|
########################################
|
@@ -10351,6 +10321,18 @@ def bind_commands(
|
|
10351
10321
|
return inj.as_bindings(*lst)
|
10352
10322
|
|
10353
10323
|
|
10324
|
+
########################################
|
10325
|
+
# ../deploy/inject_.py
|
10326
|
+
|
10327
|
+
|
10328
|
+
def bind_deploy_manager(cls: type) -> InjectorBindings:
|
10329
|
+
return inj.as_bindings(
|
10330
|
+
inj.bind(cls, singleton=True),
|
10331
|
+
|
10332
|
+
*([inj.bind(DeployPathOwner, to_key=cls, array=True)] if issubclass(cls, DeployPathOwner) else []),
|
10333
|
+
)
|
10334
|
+
|
10335
|
+
|
10354
10336
|
########################################
|
10355
10337
|
# ../deploy/paths/manager.py
|
10356
10338
|
|
@@ -10578,15 +10560,143 @@ def bind_interp_providers() -> InjectorBindings:
|
|
10578
10560
|
|
10579
10561
|
|
10580
10562
|
########################################
|
10581
|
-
# ../../../omdev/interp/pyenv/
|
10563
|
+
# ../../../omdev/interp/pyenv/provider.py
|
10582
10564
|
|
10583
10565
|
|
10584
|
-
|
10585
|
-
|
10586
|
-
|
10566
|
+
class PyenvInterpProvider(InterpProvider):
|
10567
|
+
@dc.dataclass(frozen=True)
|
10568
|
+
class Options:
|
10569
|
+
inspect: bool = False
|
10587
10570
|
|
10588
|
-
|
10589
|
-
|
10571
|
+
try_update: bool = False
|
10572
|
+
|
10573
|
+
def __init__(
|
10574
|
+
self,
|
10575
|
+
options: Options = Options(),
|
10576
|
+
*,
|
10577
|
+
pyenv: Pyenv,
|
10578
|
+
inspector: InterpInspector,
|
10579
|
+
log: ta.Optional[logging.Logger] = None,
|
10580
|
+
) -> None:
|
10581
|
+
super().__init__()
|
10582
|
+
|
10583
|
+
self._options = options
|
10584
|
+
|
10585
|
+
self._pyenv = pyenv
|
10586
|
+
self._inspector = inspector
|
10587
|
+
self._log = log
|
10588
|
+
|
10589
|
+
#
|
10590
|
+
|
10591
|
+
@staticmethod
|
10592
|
+
def guess_version(s: str) -> ta.Optional[InterpVersion]:
|
10593
|
+
def strip_sfx(s: str, sfx: str) -> ta.Tuple[str, bool]:
|
10594
|
+
if s.endswith(sfx):
|
10595
|
+
return s[:-len(sfx)], True
|
10596
|
+
return s, False
|
10597
|
+
ok = {}
|
10598
|
+
s, ok['debug'] = strip_sfx(s, '-debug')
|
10599
|
+
s, ok['threaded'] = strip_sfx(s, 't')
|
10600
|
+
try:
|
10601
|
+
v = Version(s)
|
10602
|
+
except InvalidVersion:
|
10603
|
+
return None
|
10604
|
+
return InterpVersion(v, InterpOpts(**ok))
|
10605
|
+
|
10606
|
+
class Installed(ta.NamedTuple):
|
10607
|
+
name: str
|
10608
|
+
exe: str
|
10609
|
+
version: InterpVersion
|
10610
|
+
|
10611
|
+
async def _make_installed(self, vn: str, ep: str) -> ta.Optional[Installed]:
|
10612
|
+
iv: ta.Optional[InterpVersion]
|
10613
|
+
if self._options.inspect:
|
10614
|
+
try:
|
10615
|
+
iv = check.not_none(await self._inspector.inspect(ep)).iv
|
10616
|
+
except Exception as e: # noqa
|
10617
|
+
return None
|
10618
|
+
else:
|
10619
|
+
iv = self.guess_version(vn)
|
10620
|
+
if iv is None:
|
10621
|
+
return None
|
10622
|
+
return PyenvInterpProvider.Installed(
|
10623
|
+
name=vn,
|
10624
|
+
exe=ep,
|
10625
|
+
version=iv,
|
10626
|
+
)
|
10627
|
+
|
10628
|
+
async def installed(self) -> ta.Sequence[Installed]:
|
10629
|
+
ret: ta.List[PyenvInterpProvider.Installed] = []
|
10630
|
+
for vn, ep in await self._pyenv.version_exes():
|
10631
|
+
if (i := await self._make_installed(vn, ep)) is None:
|
10632
|
+
if self._log is not None:
|
10633
|
+
self._log.debug('Invalid pyenv version: %s', vn)
|
10634
|
+
continue
|
10635
|
+
ret.append(i)
|
10636
|
+
return ret
|
10637
|
+
|
10638
|
+
#
|
10639
|
+
|
10640
|
+
async def get_installed_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
|
10641
|
+
return [i.version for i in await self.installed()]
|
10642
|
+
|
10643
|
+
async def get_installed_version(self, version: InterpVersion) -> Interp:
|
10644
|
+
for i in await self.installed():
|
10645
|
+
if i.version == version:
|
10646
|
+
return Interp(
|
10647
|
+
exe=i.exe,
|
10648
|
+
version=i.version,
|
10649
|
+
)
|
10650
|
+
raise KeyError(version)
|
10651
|
+
|
10652
|
+
#
|
10653
|
+
|
10654
|
+
async def _get_installable_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
|
10655
|
+
lst = []
|
10656
|
+
|
10657
|
+
for vs in await self._pyenv.installable_versions():
|
10658
|
+
if (iv := self.guess_version(vs)) is None:
|
10659
|
+
continue
|
10660
|
+
if iv.opts.debug:
|
10661
|
+
raise Exception('Pyenv installable versions not expected to have debug suffix')
|
10662
|
+
for d in [False, True]:
|
10663
|
+
lst.append(dc.replace(iv, opts=dc.replace(iv.opts, debug=d)))
|
10664
|
+
|
10665
|
+
return lst
|
10666
|
+
|
10667
|
+
async def get_installable_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
|
10668
|
+
lst = await self._get_installable_versions(spec)
|
10669
|
+
|
10670
|
+
if self._options.try_update and not any(v in spec for v in lst):
|
10671
|
+
if self._pyenv.update():
|
10672
|
+
lst = await self._get_installable_versions(spec)
|
10673
|
+
|
10674
|
+
return lst
|
10675
|
+
|
10676
|
+
async def install_version(self, version: InterpVersion) -> Interp:
|
10677
|
+
inst_version = str(version.version)
|
10678
|
+
inst_opts = version.opts
|
10679
|
+
if inst_opts.threaded:
|
10680
|
+
inst_version += 't'
|
10681
|
+
inst_opts = dc.replace(inst_opts, threaded=False)
|
10682
|
+
|
10683
|
+
installer = PyenvVersionInstaller(
|
10684
|
+
inst_version,
|
10685
|
+
interp_opts=inst_opts,
|
10686
|
+
pyenv=self._pyenv,
|
10687
|
+
)
|
10688
|
+
|
10689
|
+
exe = await installer.install()
|
10690
|
+
return Interp(exe, version)
|
10691
|
+
|
10692
|
+
|
10693
|
+
########################################
|
10694
|
+
# ../deploy/conf/inject.py
|
10695
|
+
|
10696
|
+
|
10697
|
+
def bind_deploy_conf() -> InjectorBindings:
|
10698
|
+
lst: ta.List[InjectorBindingOrBindings] = [
|
10699
|
+
bind_deploy_manager(DeployConfManager),
|
10590
10700
|
]
|
10591
10701
|
|
10592
10702
|
return inj.as_bindings(*lst)
|
@@ -10955,6 +11065,50 @@ class SshManageTargetConnector(ManageTargetConnector):
|
|
10955
11065
|
yield rce
|
10956
11066
|
|
10957
11067
|
|
11068
|
+
########################################
|
11069
|
+
# ../../../omdev/interp/pyenv/inject.py
|
11070
|
+
|
11071
|
+
|
11072
|
+
def bind_interp_pyenv() -> InjectorBindings:
|
11073
|
+
lst: ta.List[InjectorBindingOrBindings] = [
|
11074
|
+
inj.bind(Pyenv, singleton=True),
|
11075
|
+
|
11076
|
+
inj.bind(PyenvInterpProvider, singleton=True),
|
11077
|
+
inj.bind(InterpProvider, to_key=PyenvInterpProvider, array=True),
|
11078
|
+
]
|
11079
|
+
|
11080
|
+
return inj.as_bindings(*lst)
|
11081
|
+
|
11082
|
+
|
11083
|
+
########################################
|
11084
|
+
# ../targets/inject.py
|
11085
|
+
|
11086
|
+
|
11087
|
+
def bind_targets() -> InjectorBindings:
|
11088
|
+
lst: ta.List[InjectorBindingOrBindings] = [
|
11089
|
+
inj.bind(LocalManageTargetConnector, singleton=True),
|
11090
|
+
inj.bind(DockerManageTargetConnector, singleton=True),
|
11091
|
+
inj.bind(SshManageTargetConnector, singleton=True),
|
11092
|
+
|
11093
|
+
inj.bind(TypeSwitchedManageTargetConnector, singleton=True),
|
11094
|
+
inj.bind(ManageTargetConnector, to_key=TypeSwitchedManageTargetConnector),
|
11095
|
+
]
|
11096
|
+
|
11097
|
+
#
|
11098
|
+
|
11099
|
+
def provide_manage_target_connector_map(injector: Injector) -> ManageTargetConnectorMap:
|
11100
|
+
return ManageTargetConnectorMap({
|
11101
|
+
LocalManageTarget: injector[LocalManageTargetConnector],
|
11102
|
+
DockerManageTarget: injector[DockerManageTargetConnector],
|
11103
|
+
SshManageTarget: injector[SshManageTargetConnector],
|
11104
|
+
})
|
11105
|
+
lst.append(inj.bind(provide_manage_target_connector_map, singleton=True))
|
11106
|
+
|
11107
|
+
#
|
11108
|
+
|
11109
|
+
return inj.as_bindings(*lst)
|
11110
|
+
|
11111
|
+
|
10958
11112
|
########################################
|
10959
11113
|
# ../../../omdev/interp/inject.py
|
10960
11114
|
|
@@ -10996,35 +11150,6 @@ def bind_interp() -> InjectorBindings:
|
|
10996
11150
|
return inj.as_bindings(*lst)
|
10997
11151
|
|
10998
11152
|
|
10999
|
-
########################################
|
11000
|
-
# ../targets/inject.py
|
11001
|
-
|
11002
|
-
|
11003
|
-
def bind_targets() -> InjectorBindings:
|
11004
|
-
lst: ta.List[InjectorBindingOrBindings] = [
|
11005
|
-
inj.bind(LocalManageTargetConnector, singleton=True),
|
11006
|
-
inj.bind(DockerManageTargetConnector, singleton=True),
|
11007
|
-
inj.bind(SshManageTargetConnector, singleton=True),
|
11008
|
-
|
11009
|
-
inj.bind(TypeSwitchedManageTargetConnector, singleton=True),
|
11010
|
-
inj.bind(ManageTargetConnector, to_key=TypeSwitchedManageTargetConnector),
|
11011
|
-
]
|
11012
|
-
|
11013
|
-
#
|
11014
|
-
|
11015
|
-
def provide_manage_target_connector_map(injector: Injector) -> ManageTargetConnectorMap:
|
11016
|
-
return ManageTargetConnectorMap({
|
11017
|
-
LocalManageTarget: injector[LocalManageTargetConnector],
|
11018
|
-
DockerManageTarget: injector[DockerManageTargetConnector],
|
11019
|
-
SshManageTarget: injector[SshManageTargetConnector],
|
11020
|
-
})
|
11021
|
-
lst.append(inj.bind(provide_manage_target_connector_map, singleton=True))
|
11022
|
-
|
11023
|
-
#
|
11024
|
-
|
11025
|
-
return inj.as_bindings(*lst)
|
11026
|
-
|
11027
|
-
|
11028
11153
|
########################################
|
11029
11154
|
# ../../../omdev/interp/default.py
|
11030
11155
|
|
@@ -11422,6 +11547,8 @@ def bind_deploy(
|
|
11422
11547
|
lst: ta.List[InjectorBindingOrBindings] = [
|
11423
11548
|
inj.bind(deploy_config),
|
11424
11549
|
|
11550
|
+
bind_deploy_conf(),
|
11551
|
+
|
11425
11552
|
bind_deploy_paths(),
|
11426
11553
|
|
11427
11554
|
bind_deploy_scope(),
|
@@ -11429,27 +11556,16 @@ def bind_deploy(
|
|
11429
11556
|
|
11430
11557
|
#
|
11431
11558
|
|
11432
|
-
def bind_manager(cls: type) -> InjectorBindings:
|
11433
|
-
return inj.as_bindings(
|
11434
|
-
inj.bind(cls, singleton=True),
|
11435
|
-
|
11436
|
-
*([inj.bind(DeployPathOwner, to_key=cls, array=True)] if issubclass(cls, DeployPathOwner) else []),
|
11437
|
-
)
|
11438
|
-
|
11439
|
-
#
|
11440
|
-
|
11441
11559
|
lst.extend([
|
11442
|
-
|
11443
|
-
|
11444
|
-
bind_manager(DeployConfManager),
|
11560
|
+
bind_deploy_manager(DeployAppManager),
|
11445
11561
|
|
11446
|
-
|
11562
|
+
bind_deploy_manager(DeployGitManager),
|
11447
11563
|
|
11448
|
-
|
11564
|
+
bind_deploy_manager(DeployManager),
|
11449
11565
|
|
11450
|
-
|
11566
|
+
bind_deploy_manager(DeployTmpManager),
|
11451
11567
|
|
11452
|
-
|
11568
|
+
bind_deploy_manager(DeployVenvManager),
|
11453
11569
|
])
|
11454
11570
|
|
11455
11571
|
#
|