ominfra 0.0.0.dev181__py3-none-any.whl → 0.0.0.dev183__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
#
|