ominfra 0.0.0.dev158__py3-none-any.whl → 0.0.0.dev159__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- ominfra/manage/deploy/apps.py +11 -8
- ominfra/manage/deploy/atomics.py +207 -0
- ominfra/manage/deploy/git.py +24 -28
- ominfra/manage/deploy/inject.py +11 -0
- ominfra/manage/deploy/paths.py +41 -3
- ominfra/manage/deploy/specs.py +10 -0
- ominfra/manage/deploy/tmp.py +46 -0
- ominfra/manage/deploy/types.py +1 -0
- ominfra/manage/deploy/venvs.py +6 -1
- ominfra/scripts/journald2aws.py +11 -9
- ominfra/scripts/manage.py +343 -44
- ominfra/scripts/supervisor.py +11 -9
- {ominfra-0.0.0.dev158.dist-info → ominfra-0.0.0.dev159.dist-info}/METADATA +3 -3
- {ominfra-0.0.0.dev158.dist-info → ominfra-0.0.0.dev159.dist-info}/RECORD +18 -16
- {ominfra-0.0.0.dev158.dist-info → ominfra-0.0.0.dev159.dist-info}/LICENSE +0 -0
- {ominfra-0.0.0.dev158.dist-info → ominfra-0.0.0.dev159.dist-info}/WHEEL +0 -0
- {ominfra-0.0.0.dev158.dist-info → ominfra-0.0.0.dev159.dist-info}/entry_points.txt +0 -0
- {ominfra-0.0.0.dev158.dist-info → ominfra-0.0.0.dev159.dist-info}/top_level.txt +0 -0
ominfra/scripts/manage.py
CHANGED
@@ -24,6 +24,7 @@ import decimal
|
|
24
24
|
import enum
|
25
25
|
import fractions
|
26
26
|
import functools
|
27
|
+
import hashlib
|
27
28
|
import inspect
|
28
29
|
import itertools
|
29
30
|
import json
|
@@ -41,6 +42,7 @@ import string
|
|
41
42
|
import struct
|
42
43
|
import subprocess
|
43
44
|
import sys
|
45
|
+
import tempfile
|
44
46
|
import threading
|
45
47
|
import time
|
46
48
|
import traceback
|
@@ -98,6 +100,10 @@ CallableVersionOperator = ta.Callable[['Version', str], bool]
|
|
98
100
|
CommandT = ta.TypeVar('CommandT', bound='Command')
|
99
101
|
CommandOutputT = ta.TypeVar('CommandOutputT', bound='Command.Output')
|
100
102
|
|
103
|
+
# deploy/atomics.py
|
104
|
+
DeployAtomicPathSwapKind = ta.Literal['dir', 'file']
|
105
|
+
DeployAtomicPathSwapState = ta.Literal['open', 'committed', 'aborted'] # ta.TypeAlias
|
106
|
+
|
101
107
|
# deploy/paths.py
|
102
108
|
DeployPathKind = ta.Literal['dir', 'file'] # ta.TypeAlias
|
103
109
|
DeployPathPlaceholder = ta.Literal['app', 'tag'] # ta.TypeAlias
|
@@ -1382,6 +1388,7 @@ DeployHome = ta.NewType('DeployHome', str)
|
|
1382
1388
|
DeployApp = ta.NewType('DeployApp', str)
|
1383
1389
|
DeployTag = ta.NewType('DeployTag', str)
|
1384
1390
|
DeployRev = ta.NewType('DeployRev', str)
|
1391
|
+
DeployKey = ta.NewType('DeployKey', str)
|
1385
1392
|
|
1386
1393
|
|
1387
1394
|
class DeployAppTag(ta.NamedTuple):
|
@@ -4053,6 +4060,204 @@ def build_command_name_map(crs: CommandRegistrations) -> CommandNameMap:
|
|
4053
4060
|
return CommandNameMap(dct)
|
4054
4061
|
|
4055
4062
|
|
4063
|
+
########################################
|
4064
|
+
# ../deploy/atomics.py
|
4065
|
+
|
4066
|
+
|
4067
|
+
##
|
4068
|
+
|
4069
|
+
|
4070
|
+
class DeployAtomicPathSwap(abc.ABC):
|
4071
|
+
def __init__(
|
4072
|
+
self,
|
4073
|
+
kind: DeployAtomicPathSwapKind,
|
4074
|
+
dst_path: str,
|
4075
|
+
*,
|
4076
|
+
auto_commit: bool = False,
|
4077
|
+
) -> None:
|
4078
|
+
super().__init__()
|
4079
|
+
|
4080
|
+
self._kind = kind
|
4081
|
+
self._dst_path = dst_path
|
4082
|
+
self._auto_commit = auto_commit
|
4083
|
+
|
4084
|
+
self._state: DeployAtomicPathSwapState = 'open'
|
4085
|
+
|
4086
|
+
def __repr__(self) -> str:
|
4087
|
+
return attr_repr(self, 'kind', 'dst_path', 'tmp_path')
|
4088
|
+
|
4089
|
+
@property
|
4090
|
+
def kind(self) -> DeployAtomicPathSwapKind:
|
4091
|
+
return self._kind
|
4092
|
+
|
4093
|
+
@property
|
4094
|
+
def dst_path(self) -> str:
|
4095
|
+
return self._dst_path
|
4096
|
+
|
4097
|
+
@property
|
4098
|
+
@abc.abstractmethod
|
4099
|
+
def tmp_path(self) -> str:
|
4100
|
+
raise NotImplementedError
|
4101
|
+
|
4102
|
+
#
|
4103
|
+
|
4104
|
+
@property
|
4105
|
+
def state(self) -> DeployAtomicPathSwapState:
|
4106
|
+
return self._state
|
4107
|
+
|
4108
|
+
def _check_state(self, *states: DeployAtomicPathSwapState) -> None:
|
4109
|
+
if self._state not in states:
|
4110
|
+
raise RuntimeError(f'Atomic path swap not in correct state: {self._state}, {states}')
|
4111
|
+
|
4112
|
+
#
|
4113
|
+
|
4114
|
+
@abc.abstractmethod
|
4115
|
+
def _commit(self) -> None:
|
4116
|
+
raise NotImplementedError
|
4117
|
+
|
4118
|
+
def commit(self) -> None:
|
4119
|
+
if self._state == 'committed':
|
4120
|
+
return
|
4121
|
+
self._check_state('open')
|
4122
|
+
try:
|
4123
|
+
self._commit()
|
4124
|
+
except Exception: # noqa
|
4125
|
+
self._abort()
|
4126
|
+
raise
|
4127
|
+
else:
|
4128
|
+
self._state = 'committed'
|
4129
|
+
|
4130
|
+
#
|
4131
|
+
|
4132
|
+
@abc.abstractmethod
|
4133
|
+
def _abort(self) -> None:
|
4134
|
+
raise NotImplementedError
|
4135
|
+
|
4136
|
+
def abort(self) -> None:
|
4137
|
+
if self._state == 'aborted':
|
4138
|
+
return
|
4139
|
+
self._abort()
|
4140
|
+
self._state = 'aborted'
|
4141
|
+
|
4142
|
+
#
|
4143
|
+
|
4144
|
+
def __enter__(self) -> 'DeployAtomicPathSwap':
|
4145
|
+
return self
|
4146
|
+
|
4147
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
4148
|
+
if (
|
4149
|
+
exc_type is None and
|
4150
|
+
self._auto_commit and
|
4151
|
+
self._state == 'open'
|
4152
|
+
):
|
4153
|
+
self.commit()
|
4154
|
+
else:
|
4155
|
+
self.abort()
|
4156
|
+
|
4157
|
+
|
4158
|
+
#
|
4159
|
+
|
4160
|
+
|
4161
|
+
class DeployAtomicPathSwapping(abc.ABC):
|
4162
|
+
@abc.abstractmethod
|
4163
|
+
def begin_atomic_path_swap(
|
4164
|
+
self,
|
4165
|
+
kind: DeployAtomicPathSwapKind,
|
4166
|
+
dst_path: str,
|
4167
|
+
*,
|
4168
|
+
name_hint: ta.Optional[str] = None,
|
4169
|
+
make_dirs: bool = False,
|
4170
|
+
**kwargs: ta.Any,
|
4171
|
+
) -> DeployAtomicPathSwap:
|
4172
|
+
raise NotImplementedError
|
4173
|
+
|
4174
|
+
|
4175
|
+
##
|
4176
|
+
|
4177
|
+
|
4178
|
+
class OsRenameDeployAtomicPathSwap(DeployAtomicPathSwap):
|
4179
|
+
def __init__(
|
4180
|
+
self,
|
4181
|
+
kind: DeployAtomicPathSwapKind,
|
4182
|
+
dst_path: str,
|
4183
|
+
tmp_path: str,
|
4184
|
+
**kwargs: ta.Any,
|
4185
|
+
) -> None:
|
4186
|
+
if kind == 'dir':
|
4187
|
+
check.state(os.path.isdir(tmp_path))
|
4188
|
+
elif kind == 'file':
|
4189
|
+
check.state(os.path.isfile(tmp_path))
|
4190
|
+
else:
|
4191
|
+
raise TypeError(kind)
|
4192
|
+
|
4193
|
+
super().__init__(
|
4194
|
+
kind,
|
4195
|
+
dst_path,
|
4196
|
+
**kwargs,
|
4197
|
+
)
|
4198
|
+
|
4199
|
+
self._tmp_path = tmp_path
|
4200
|
+
|
4201
|
+
@property
|
4202
|
+
def tmp_path(self) -> str:
|
4203
|
+
return self._tmp_path
|
4204
|
+
|
4205
|
+
def _commit(self) -> None:
|
4206
|
+
os.rename(self._tmp_path, self._dst_path)
|
4207
|
+
|
4208
|
+
def _abort(self) -> None:
|
4209
|
+
shutil.rmtree(self._tmp_path, ignore_errors=True)
|
4210
|
+
|
4211
|
+
|
4212
|
+
class TempDirDeployAtomicPathSwapping(DeployAtomicPathSwapping):
|
4213
|
+
def __init__(
|
4214
|
+
self,
|
4215
|
+
*,
|
4216
|
+
temp_dir: ta.Optional[str] = None,
|
4217
|
+
root_dir: ta.Optional[str] = None,
|
4218
|
+
) -> None:
|
4219
|
+
super().__init__()
|
4220
|
+
|
4221
|
+
if root_dir is not None:
|
4222
|
+
root_dir = os.path.abspath(root_dir)
|
4223
|
+
self._root_dir = root_dir
|
4224
|
+
self._temp_dir = temp_dir
|
4225
|
+
|
4226
|
+
def begin_atomic_path_swap(
|
4227
|
+
self,
|
4228
|
+
kind: DeployAtomicPathSwapKind,
|
4229
|
+
dst_path: str,
|
4230
|
+
*,
|
4231
|
+
name_hint: ta.Optional[str] = None,
|
4232
|
+
make_dirs: bool = False,
|
4233
|
+
**kwargs: ta.Any,
|
4234
|
+
) -> DeployAtomicPathSwap:
|
4235
|
+
dst_path = os.path.abspath(dst_path)
|
4236
|
+
if self._root_dir is not None and not dst_path.startswith(check.non_empty_str(self._root_dir)):
|
4237
|
+
raise RuntimeError(f'Atomic path swap dst must be in root dir: {dst_path}, {self._root_dir}')
|
4238
|
+
|
4239
|
+
dst_dir = os.path.dirname(dst_path)
|
4240
|
+
if make_dirs:
|
4241
|
+
os.makedirs(dst_dir, exist_ok=True)
|
4242
|
+
if not os.path.isdir(dst_dir):
|
4243
|
+
raise RuntimeError(f'Atomic path swap dst dir does not exist: {dst_dir}')
|
4244
|
+
|
4245
|
+
if kind == 'dir':
|
4246
|
+
tmp_path = tempfile.mkdtemp(prefix=name_hint, dir=self._temp_dir)
|
4247
|
+
elif kind == 'file':
|
4248
|
+
fd, tmp_path = tempfile.mkstemp(prefix=name_hint, dir=self._temp_dir)
|
4249
|
+
os.close(fd)
|
4250
|
+
else:
|
4251
|
+
raise TypeError(kind)
|
4252
|
+
|
4253
|
+
return OsRenameDeployAtomicPathSwap(
|
4254
|
+
kind,
|
4255
|
+
dst_path,
|
4256
|
+
tmp_path,
|
4257
|
+
**kwargs,
|
4258
|
+
)
|
4259
|
+
|
4260
|
+
|
4056
4261
|
########################################
|
4057
4262
|
# ../deploy/paths.py
|
4058
4263
|
"""
|
@@ -4070,6 +4275,8 @@ def build_command_name_map(crs: CommandRegistrations) -> CommandNameMap:
|
|
4070
4275
|
/venv
|
4071
4276
|
/<appplaceholder>
|
4072
4277
|
|
4278
|
+
/tmp
|
4279
|
+
|
4073
4280
|
?
|
4074
4281
|
/logs
|
4075
4282
|
/wrmsr--omlish--<placeholder>
|
@@ -4227,6 +4434,8 @@ class DeployPath:
|
|
4227
4434
|
parts: ta.Sequence[DeployPathPart]
|
4228
4435
|
|
4229
4436
|
def __post_init__(self) -> None:
|
4437
|
+
hash(self)
|
4438
|
+
|
4230
4439
|
check.not_empty(self.parts)
|
4231
4440
|
for p in self.parts[:-1]:
|
4232
4441
|
check.equal(p.kind, 'dir')
|
@@ -4261,10 +4470,10 @@ class DeployPath:
|
|
4261
4470
|
else:
|
4262
4471
|
tail_parse = FileDeployPathPart.parse
|
4263
4472
|
ps = check.non_empty_str(s).split('/')
|
4264
|
-
return cls(
|
4473
|
+
return cls((
|
4265
4474
|
*([DirDeployPathPart.parse(p) for p in ps[:-1]] if len(ps) > 1 else []),
|
4266
4475
|
tail_parse(ps[-1]),
|
4267
|
-
|
4476
|
+
))
|
4268
4477
|
|
4269
4478
|
|
4270
4479
|
##
|
@@ -4272,10 +4481,41 @@ class DeployPath:
|
|
4272
4481
|
|
4273
4482
|
class DeployPathOwner(abc.ABC):
|
4274
4483
|
@abc.abstractmethod
|
4275
|
-
def
|
4484
|
+
def get_owned_deploy_paths(self) -> ta.AbstractSet[DeployPath]:
|
4276
4485
|
raise NotImplementedError
|
4277
4486
|
|
4278
4487
|
|
4488
|
+
class SingleDirDeployPathOwner(DeployPathOwner, abc.ABC):
|
4489
|
+
def __init__(
|
4490
|
+
self,
|
4491
|
+
*args: ta.Any,
|
4492
|
+
owned_dir: str,
|
4493
|
+
deploy_home: ta.Optional[DeployHome],
|
4494
|
+
**kwargs: ta.Any,
|
4495
|
+
) -> None:
|
4496
|
+
super().__init__(*args, **kwargs)
|
4497
|
+
|
4498
|
+
check.not_in('/', owned_dir)
|
4499
|
+
self._owned_dir: str = check.non_empty_str(owned_dir)
|
4500
|
+
|
4501
|
+
self._deploy_home = deploy_home
|
4502
|
+
|
4503
|
+
self._owned_deploy_paths = frozenset([DeployPath.parse(self._owned_dir + '/')])
|
4504
|
+
|
4505
|
+
@cached_nullary
|
4506
|
+
def _dir(self) -> str:
|
4507
|
+
return os.path.join(check.non_empty_str(self._deploy_home), self._owned_dir)
|
4508
|
+
|
4509
|
+
@cached_nullary
|
4510
|
+
def _make_dir(self) -> str:
|
4511
|
+
if not os.path.isdir(d := self._dir()):
|
4512
|
+
os.makedirs(d, exist_ok=True)
|
4513
|
+
return d
|
4514
|
+
|
4515
|
+
def get_owned_deploy_paths(self) -> ta.AbstractSet[DeployPath]:
|
4516
|
+
return self._owned_deploy_paths
|
4517
|
+
|
4518
|
+
|
4279
4519
|
########################################
|
4280
4520
|
# ../deploy/specs.py
|
4281
4521
|
|
@@ -4303,6 +4543,13 @@ class DeploySpec:
|
|
4303
4543
|
repo: DeployGitRepo
|
4304
4544
|
rev: DeployRev
|
4305
4545
|
|
4546
|
+
def __post_init__(self) -> None:
|
4547
|
+
hash(self)
|
4548
|
+
|
4549
|
+
@cached_nullary
|
4550
|
+
def key(self) -> DeployKey:
|
4551
|
+
return DeployKey(hashlib.sha256(repr(self).encode('utf-8')).hexdigest()[:8])
|
4552
|
+
|
4306
4553
|
|
4307
4554
|
########################################
|
4308
4555
|
# ../remote/config.py
|
@@ -6210,12 +6457,12 @@ def is_debugger_attached() -> bool:
|
|
6210
6457
|
return any(frame[1].endswith('pydevd.py') for frame in inspect.stack())
|
6211
6458
|
|
6212
6459
|
|
6213
|
-
|
6460
|
+
LITE_REQUIRED_PYTHON_VERSION = (3, 8)
|
6214
6461
|
|
6215
6462
|
|
6216
|
-
def
|
6217
|
-
if sys.version_info <
|
6218
|
-
raise OSError(f'Requires python {
|
6463
|
+
def check_lite_runtime_version() -> None:
|
6464
|
+
if sys.version_info < LITE_REQUIRED_PYTHON_VERSION:
|
6465
|
+
raise OSError(f'Requires python {LITE_REQUIRED_PYTHON_VERSION}, got {sys.version_info} from {sys.executable}') # noqa
|
6219
6466
|
|
6220
6467
|
|
6221
6468
|
########################################
|
@@ -6523,6 +6770,44 @@ class DeployCommandExecutor(CommandExecutor[DeployCommand, DeployCommand.Output]
|
|
6523
6770
|
return DeployCommand.Output()
|
6524
6771
|
|
6525
6772
|
|
6773
|
+
########################################
|
6774
|
+
# ../deploy/tmp.py
|
6775
|
+
|
6776
|
+
|
6777
|
+
class DeployTmpManager(
|
6778
|
+
SingleDirDeployPathOwner,
|
6779
|
+
DeployAtomicPathSwapping,
|
6780
|
+
):
|
6781
|
+
def __init__(
|
6782
|
+
self,
|
6783
|
+
*,
|
6784
|
+
deploy_home: ta.Optional[DeployHome] = None,
|
6785
|
+
) -> None:
|
6786
|
+
super().__init__(
|
6787
|
+
owned_dir='tmp',
|
6788
|
+
deploy_home=deploy_home,
|
6789
|
+
)
|
6790
|
+
|
6791
|
+
@cached_nullary
|
6792
|
+
def _swapping(self) -> DeployAtomicPathSwapping:
|
6793
|
+
return TempDirDeployAtomicPathSwapping(
|
6794
|
+
temp_dir=self._make_dir(),
|
6795
|
+
root_dir=check.non_empty_str(self._deploy_home),
|
6796
|
+
)
|
6797
|
+
|
6798
|
+
def begin_atomic_path_swap(
|
6799
|
+
self,
|
6800
|
+
kind: DeployAtomicPathSwapKind,
|
6801
|
+
dst_path: str,
|
6802
|
+
**kwargs: ta.Any,
|
6803
|
+
) -> DeployAtomicPathSwap:
|
6804
|
+
return self._swapping().begin_atomic_path_swap(
|
6805
|
+
kind,
|
6806
|
+
dst_path,
|
6807
|
+
**kwargs,
|
6808
|
+
)
|
6809
|
+
|
6810
|
+
|
6526
6811
|
########################################
|
6527
6812
|
# ../marshal.py
|
6528
6813
|
|
@@ -6630,6 +6915,7 @@ TODO:
|
|
6630
6915
|
- structured
|
6631
6916
|
- prefixed
|
6632
6917
|
- debug
|
6918
|
+
- optional noisy? noisy will never be lite - some kinda configure_standard callback mechanism?
|
6633
6919
|
"""
|
6634
6920
|
|
6635
6921
|
|
@@ -6666,8 +6952,9 @@ class StandardLogFormatter(logging.Formatter):
|
|
6666
6952
|
##
|
6667
6953
|
|
6668
6954
|
|
6669
|
-
class
|
6670
|
-
|
6955
|
+
class StandardConfiguredLogHandler(ProxyLogHandler):
|
6956
|
+
def __init_subclass__(cls, **kwargs):
|
6957
|
+
raise TypeError('This class serves only as a marker and should not be subclassed.')
|
6671
6958
|
|
6672
6959
|
|
6673
6960
|
##
|
@@ -6698,7 +6985,7 @@ def configure_standard_logging(
|
|
6698
6985
|
target: ta.Optional[logging.Logger] = None,
|
6699
6986
|
force: bool = False,
|
6700
6987
|
handler_factory: ta.Optional[ta.Callable[[], logging.Handler]] = None,
|
6701
|
-
) -> ta.Optional[
|
6988
|
+
) -> ta.Optional[StandardConfiguredLogHandler]:
|
6702
6989
|
with _locking_logging_module_lock():
|
6703
6990
|
if target is None:
|
6704
6991
|
target = logging.root
|
@@ -6706,7 +6993,7 @@ def configure_standard_logging(
|
|
6706
6993
|
#
|
6707
6994
|
|
6708
6995
|
if not force:
|
6709
|
-
if any(isinstance(h,
|
6996
|
+
if any(isinstance(h, StandardConfiguredLogHandler) for h in list(target.handlers)):
|
6710
6997
|
return None
|
6711
6998
|
|
6712
6999
|
#
|
@@ -6740,7 +7027,7 @@ def configure_standard_logging(
|
|
6740
7027
|
|
6741
7028
|
#
|
6742
7029
|
|
6743
|
-
return
|
7030
|
+
return StandardConfiguredLogHandler(handler)
|
6744
7031
|
|
6745
7032
|
|
6746
7033
|
########################################
|
@@ -7907,27 +8194,22 @@ github.com/wrmsr/omlish@rev
|
|
7907
8194
|
##
|
7908
8195
|
|
7909
8196
|
|
7910
|
-
class DeployGitManager(
|
8197
|
+
class DeployGitManager(SingleDirDeployPathOwner):
|
7911
8198
|
def __init__(
|
7912
8199
|
self,
|
7913
8200
|
*,
|
7914
8201
|
deploy_home: ta.Optional[DeployHome] = None,
|
8202
|
+
atomics: DeployAtomicPathSwapping,
|
7915
8203
|
) -> None:
|
7916
|
-
super().__init__(
|
8204
|
+
super().__init__(
|
8205
|
+
owned_dir='git',
|
8206
|
+
deploy_home=deploy_home,
|
8207
|
+
)
|
7917
8208
|
|
7918
|
-
self.
|
8209
|
+
self._atomics = atomics
|
7919
8210
|
|
7920
8211
|
self._repo_dirs: ta.Dict[DeployGitRepo, DeployGitManager.RepoDir] = {}
|
7921
8212
|
|
7922
|
-
@cached_nullary
|
7923
|
-
def _dir(self) -> str:
|
7924
|
-
return os.path.join(check.non_empty_str(self._deploy_home), 'git')
|
7925
|
-
|
7926
|
-
def get_deploy_paths(self) -> ta.AbstractSet[DeployPath]:
|
7927
|
-
return {
|
7928
|
-
DeployPath.parse('git'),
|
7929
|
-
}
|
7930
|
-
|
7931
8213
|
class RepoDir:
|
7932
8214
|
def __init__(
|
7933
8215
|
self,
|
@@ -7939,7 +8221,7 @@ class DeployGitManager(DeployPathOwner):
|
|
7939
8221
|
self._git = git
|
7940
8222
|
self._repo = repo
|
7941
8223
|
self._dir = os.path.join(
|
7942
|
-
self._git.
|
8224
|
+
self._git._make_dir(), # noqa
|
7943
8225
|
check.non_empty_str(repo.host),
|
7944
8226
|
check.non_empty_str(repo.path),
|
7945
8227
|
)
|
@@ -7976,18 +8258,20 @@ class DeployGitManager(DeployPathOwner):
|
|
7976
8258
|
|
7977
8259
|
async def checkout(self, rev: DeployRev, dst_dir: str) -> None:
|
7978
8260
|
check.state(not os.path.exists(dst_dir))
|
8261
|
+
with self._git._atomics.begin_atomic_path_swap( # noqa
|
8262
|
+
'dir',
|
8263
|
+
dst_dir,
|
8264
|
+
auto_commit=True,
|
8265
|
+
make_dirs=True,
|
8266
|
+
) as dst_swap:
|
8267
|
+
await self.fetch(rev)
|
7979
8268
|
|
7980
|
-
|
8269
|
+
dst_call = functools.partial(asyncio_subprocesses.check_call, cwd=dst_swap.tmp_path)
|
8270
|
+
await dst_call('git', 'init')
|
7981
8271
|
|
7982
|
-
|
7983
|
-
|
7984
|
-
|
7985
|
-
dst_call = functools.partial(asyncio_subprocesses.check_call, cwd=dst_dir)
|
7986
|
-
await dst_call('git', 'init')
|
7987
|
-
|
7988
|
-
await dst_call('git', 'remote', 'add', 'local', self._dir)
|
7989
|
-
await dst_call('git', 'fetch', '--depth=1', 'local', rev)
|
7990
|
-
await dst_call('git', 'checkout', rev)
|
8272
|
+
await dst_call('git', 'remote', 'add', 'local', self._dir)
|
8273
|
+
await dst_call('git', 'fetch', '--depth=1', 'local', rev)
|
8274
|
+
await dst_call('git', 'checkout', rev)
|
7991
8275
|
|
7992
8276
|
def get_repo_dir(self, repo: DeployGitRepo) -> RepoDir:
|
7993
8277
|
try:
|
@@ -8014,16 +8298,18 @@ class DeployVenvManager(DeployPathOwner):
|
|
8014
8298
|
self,
|
8015
8299
|
*,
|
8016
8300
|
deploy_home: ta.Optional[DeployHome] = None,
|
8301
|
+
atomics: DeployAtomicPathSwapping,
|
8017
8302
|
) -> None:
|
8018
8303
|
super().__init__()
|
8019
8304
|
|
8020
8305
|
self._deploy_home = deploy_home
|
8306
|
+
self._atomics = atomics
|
8021
8307
|
|
8022
8308
|
@cached_nullary
|
8023
8309
|
def _dir(self) -> str:
|
8024
8310
|
return os.path.join(check.non_empty_str(self._deploy_home), 'venvs')
|
8025
8311
|
|
8026
|
-
def
|
8312
|
+
def get_owned_deploy_paths(self) -> ta.AbstractSet[DeployPath]:
|
8027
8313
|
return {
|
8028
8314
|
DeployPath.parse('venvs/@app/@tag/'),
|
8029
8315
|
}
|
@@ -8037,6 +8323,8 @@ class DeployVenvManager(DeployPathOwner):
|
|
8037
8323
|
) -> None:
|
8038
8324
|
sys_exe = 'python3'
|
8039
8325
|
|
8326
|
+
# !! NOTE: (most) venvs cannot be relocated, so an atomic swap can't be used. it's up to the path manager to
|
8327
|
+
# garbage collect orphaned dirs.
|
8040
8328
|
await asyncio_subprocesses.check_call(sys_exe, '-m', 'venv', venv_dir)
|
8041
8329
|
|
8042
8330
|
#
|
@@ -8594,13 +8882,15 @@ def bind_commands(
|
|
8594
8882
|
|
8595
8883
|
def make_deploy_tag(
|
8596
8884
|
rev: DeployRev,
|
8597
|
-
|
8885
|
+
key: DeployKey,
|
8886
|
+
*,
|
8887
|
+
utcnow: ta.Optional[datetime.datetime] = None,
|
8598
8888
|
) -> DeployTag:
|
8599
|
-
if
|
8600
|
-
|
8601
|
-
now_fmt = '%Y%m%dT%H%M%
|
8602
|
-
now_str =
|
8603
|
-
return DeployTag('-'.join([now_str, rev]))
|
8889
|
+
if utcnow is None:
|
8890
|
+
utcnow = datetime.datetime.now(tz=datetime.timezone.utc) # noqa
|
8891
|
+
now_fmt = '%Y%m%dT%H%M%SZ'
|
8892
|
+
now_str = utcnow.strftime(now_fmt)
|
8893
|
+
return DeployTag('-'.join([now_str, rev, key]))
|
8604
8894
|
|
8605
8895
|
|
8606
8896
|
class DeployAppManager(DeployPathOwner):
|
@@ -8621,7 +8911,7 @@ class DeployAppManager(DeployPathOwner):
|
|
8621
8911
|
def _dir(self) -> str:
|
8622
8912
|
return os.path.join(check.non_empty_str(self._deploy_home), 'apps')
|
8623
8913
|
|
8624
|
-
def
|
8914
|
+
def get_owned_deploy_paths(self) -> ta.AbstractSet[DeployPath]:
|
8625
8915
|
return {
|
8626
8916
|
DeployPath.parse('apps/@app/@tag'),
|
8627
8917
|
}
|
@@ -8630,7 +8920,7 @@ class DeployAppManager(DeployPathOwner):
|
|
8630
8920
|
self,
|
8631
8921
|
spec: DeploySpec,
|
8632
8922
|
):
|
8633
|
-
app_tag = DeployAppTag(spec.app, make_deploy_tag(spec.rev))
|
8923
|
+
app_tag = DeployAppTag(spec.app, make_deploy_tag(spec.rev, spec.key()))
|
8634
8924
|
app_dir = os.path.join(self._dir(), spec.app, app_tag.tag)
|
8635
8925
|
|
8636
8926
|
#
|
@@ -9738,10 +10028,19 @@ def bind_deploy(
|
|
9738
10028
|
lst: ta.List[InjectorBindingOrBindings] = [
|
9739
10029
|
inj.bind(deploy_config),
|
9740
10030
|
|
10031
|
+
#
|
10032
|
+
|
9741
10033
|
inj.bind(DeployAppManager, singleton=True),
|
10034
|
+
|
9742
10035
|
inj.bind(DeployGitManager, singleton=True),
|
10036
|
+
|
10037
|
+
inj.bind(DeployTmpManager, singleton=True),
|
10038
|
+
inj.bind(DeployAtomicPathSwapping, to_key=DeployTmpManager),
|
10039
|
+
|
9743
10040
|
inj.bind(DeployVenvManager, singleton=True),
|
9744
10041
|
|
10042
|
+
#
|
10043
|
+
|
9745
10044
|
bind_command(DeployCommand, DeployCommandExecutor),
|
9746
10045
|
bind_command(InterpCommand, InterpCommandExecutor),
|
9747
10046
|
]
|
ominfra/scripts/supervisor.py
CHANGED
@@ -5303,12 +5303,12 @@ def is_debugger_attached() -> bool:
|
|
5303
5303
|
return any(frame[1].endswith('pydevd.py') for frame in inspect.stack())
|
5304
5304
|
|
5305
5305
|
|
5306
|
-
|
5306
|
+
LITE_REQUIRED_PYTHON_VERSION = (3, 8)
|
5307
5307
|
|
5308
5308
|
|
5309
|
-
def
|
5310
|
-
if sys.version_info <
|
5311
|
-
raise OSError(f'Requires python {
|
5309
|
+
def check_lite_runtime_version() -> None:
|
5310
|
+
if sys.version_info < LITE_REQUIRED_PYTHON_VERSION:
|
5311
|
+
raise OSError(f'Requires python {LITE_REQUIRED_PYTHON_VERSION}, got {sys.version_info} from {sys.executable}') # noqa
|
5312
5312
|
|
5313
5313
|
|
5314
5314
|
########################################
|
@@ -5760,6 +5760,7 @@ TODO:
|
|
5760
5760
|
- structured
|
5761
5761
|
- prefixed
|
5762
5762
|
- debug
|
5763
|
+
- optional noisy? noisy will never be lite - some kinda configure_standard callback mechanism?
|
5763
5764
|
"""
|
5764
5765
|
|
5765
5766
|
|
@@ -5796,8 +5797,9 @@ class StandardLogFormatter(logging.Formatter):
|
|
5796
5797
|
##
|
5797
5798
|
|
5798
5799
|
|
5799
|
-
class
|
5800
|
-
|
5800
|
+
class StandardConfiguredLogHandler(ProxyLogHandler):
|
5801
|
+
def __init_subclass__(cls, **kwargs):
|
5802
|
+
raise TypeError('This class serves only as a marker and should not be subclassed.')
|
5801
5803
|
|
5802
5804
|
|
5803
5805
|
##
|
@@ -5828,7 +5830,7 @@ def configure_standard_logging(
|
|
5828
5830
|
target: ta.Optional[logging.Logger] = None,
|
5829
5831
|
force: bool = False,
|
5830
5832
|
handler_factory: ta.Optional[ta.Callable[[], logging.Handler]] = None,
|
5831
|
-
) -> ta.Optional[
|
5833
|
+
) -> ta.Optional[StandardConfiguredLogHandler]:
|
5832
5834
|
with _locking_logging_module_lock():
|
5833
5835
|
if target is None:
|
5834
5836
|
target = logging.root
|
@@ -5836,7 +5838,7 @@ def configure_standard_logging(
|
|
5836
5838
|
#
|
5837
5839
|
|
5838
5840
|
if not force:
|
5839
|
-
if any(isinstance(h,
|
5841
|
+
if any(isinstance(h, StandardConfiguredLogHandler) for h in list(target.handlers)):
|
5840
5842
|
return None
|
5841
5843
|
|
5842
5844
|
#
|
@@ -5870,7 +5872,7 @@ def configure_standard_logging(
|
|
5870
5872
|
|
5871
5873
|
#
|
5872
5874
|
|
5873
|
-
return
|
5875
|
+
return StandardConfiguredLogHandler(handler)
|
5874
5876
|
|
5875
5877
|
|
5876
5878
|
########################################
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: ominfra
|
3
|
-
Version: 0.0.0.
|
3
|
+
Version: 0.0.0.dev159
|
4
4
|
Summary: ominfra
|
5
5
|
Author: wrmsr
|
6
6
|
License: BSD-3-Clause
|
@@ -12,8 +12,8 @@ Classifier: Operating System :: OS Independent
|
|
12
12
|
Classifier: Operating System :: POSIX
|
13
13
|
Requires-Python: >=3.12
|
14
14
|
License-File: LICENSE
|
15
|
-
Requires-Dist: omdev==0.0.0.
|
16
|
-
Requires-Dist: omlish==0.0.0.
|
15
|
+
Requires-Dist: omdev==0.0.0.dev159
|
16
|
+
Requires-Dist: omlish==0.0.0.dev159
|
17
17
|
Provides-Extra: all
|
18
18
|
Requires-Dist: paramiko~=3.5; extra == "all"
|
19
19
|
Requires-Dist: asyncssh~=2.18; extra == "all"
|