ominfra 0.0.0.dev154__py3-none-any.whl → 0.0.0.dev155__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/manage/bootstrap.py +4 -0
- ominfra/manage/bootstrap_.py +5 -0
- ominfra/manage/commands/inject.py +8 -11
- ominfra/manage/commands/{execution.py → local.py} +1 -5
- ominfra/manage/commands/ping.py +23 -0
- ominfra/manage/commands/types.py +8 -0
- ominfra/manage/deploy/apps.py +72 -0
- ominfra/manage/deploy/config.py +8 -0
- ominfra/manage/deploy/git.py +136 -0
- ominfra/manage/deploy/inject.py +21 -0
- ominfra/manage/deploy/paths.py +81 -28
- ominfra/manage/deploy/types.py +13 -0
- ominfra/manage/deploy/venvs.py +66 -0
- ominfra/manage/inject.py +20 -4
- ominfra/manage/main.py +15 -27
- ominfra/manage/remote/_main.py +1 -1
- ominfra/manage/remote/config.py +0 -2
- ominfra/manage/remote/connection.py +7 -24
- ominfra/manage/remote/execution.py +1 -1
- ominfra/manage/remote/inject.py +3 -14
- ominfra/manage/system/commands.py +22 -2
- ominfra/manage/system/config.py +3 -1
- ominfra/manage/system/inject.py +16 -6
- ominfra/manage/system/packages.py +33 -7
- ominfra/manage/system/platforms.py +72 -0
- ominfra/manage/targets/__init__.py +0 -0
- ominfra/manage/targets/connection.py +150 -0
- ominfra/manage/targets/inject.py +42 -0
- ominfra/manage/targets/targets.py +87 -0
- ominfra/scripts/journald2aws.py +24 -7
- ominfra/scripts/manage.py +1880 -438
- ominfra/scripts/supervisor.py +187 -25
- ominfra/supervisor/configs.py +163 -18
- {ominfra-0.0.0.dev154.dist-info → ominfra-0.0.0.dev155.dist-info}/METADATA +3 -3
- {ominfra-0.0.0.dev154.dist-info → ominfra-0.0.0.dev155.dist-info}/RECORD +40 -29
- ominfra/manage/system/types.py +0 -5
- /ominfra/manage/{commands → deploy}/interp.py +0 -0
- {ominfra-0.0.0.dev154.dist-info → ominfra-0.0.0.dev155.dist-info}/LICENSE +0 -0
- {ominfra-0.0.0.dev154.dist-info → ominfra-0.0.0.dev155.dist-info}/WHEEL +0 -0
- {ominfra-0.0.0.dev154.dist-info → ominfra-0.0.0.dev155.dist-info}/entry_points.txt +0 -0
- {ominfra-0.0.0.dev154.dist-info → ominfra-0.0.0.dev155.dist-info}/top_level.txt +0 -0
ominfra/scripts/manage.py
CHANGED
@@ -92,9 +92,16 @@ CallableVersionOperator = ta.Callable[['Version', str], bool]
|
|
92
92
|
CommandT = ta.TypeVar('CommandT', bound='Command')
|
93
93
|
CommandOutputT = ta.TypeVar('CommandOutputT', bound='Command.Output')
|
94
94
|
|
95
|
+
# deploy/paths.py
|
96
|
+
DeployPathKind = ta.Literal['dir', 'file'] # ta.TypeAlias
|
97
|
+
DeployPathSpec = ta.Literal['app', 'tag'] # ta.TypeAlias
|
98
|
+
|
95
99
|
# ../../omlish/argparse/cli.py
|
96
100
|
ArgparseCommandFn = ta.Callable[[], ta.Optional[int]] # ta.TypeAlias
|
97
101
|
|
102
|
+
# ../../omlish/lite/contextmanagers.py
|
103
|
+
ExitStackedT = ta.TypeVar('ExitStackedT', bound='ExitStacked')
|
104
|
+
|
98
105
|
# ../../omlish/lite/inject.py
|
99
106
|
U = ta.TypeVar('U')
|
100
107
|
InjectorKeyCls = ta.Union[type, ta.NewType]
|
@@ -528,19 +535,28 @@ class MainConfig:
|
|
528
535
|
|
529
536
|
|
530
537
|
########################################
|
531
|
-
# ../
|
538
|
+
# ../deploy/config.py
|
532
539
|
|
533
540
|
|
534
541
|
@dc.dataclass(frozen=True)
|
535
|
-
class
|
536
|
-
|
542
|
+
class DeployConfig:
|
543
|
+
deploy_home: ta.Optional[str] = None
|
537
544
|
|
538
545
|
|
539
546
|
########################################
|
540
|
-
# ../
|
547
|
+
# ../deploy/types.py
|
541
548
|
|
542
549
|
|
543
|
-
|
550
|
+
DeployHome = ta.NewType('DeployHome', str)
|
551
|
+
|
552
|
+
DeployApp = ta.NewType('DeployApp', str)
|
553
|
+
DeployTag = ta.NewType('DeployTag', str)
|
554
|
+
DeployRev = ta.NewType('DeployRev', str)
|
555
|
+
|
556
|
+
|
557
|
+
class DeployAppTag(ta.NamedTuple):
|
558
|
+
app: DeployApp
|
559
|
+
tag: DeployTag
|
544
560
|
|
545
561
|
|
546
562
|
########################################
|
@@ -1221,8 +1237,6 @@ def async_cached_nullary(fn): # ta.Callable[..., T]) -> ta.Callable[..., T]:
|
|
1221
1237
|
"""
|
1222
1238
|
TODO:
|
1223
1239
|
- def maybe(v: lang.Maybe[T])
|
1224
|
-
- patch / override lite.check ?
|
1225
|
-
- checker interface?
|
1226
1240
|
"""
|
1227
1241
|
|
1228
1242
|
|
@@ -1937,6 +1951,489 @@ def set_process_deathsig(sig: int) -> bool:
|
|
1937
1951
|
return False
|
1938
1952
|
|
1939
1953
|
|
1954
|
+
########################################
|
1955
|
+
# ../../../omlish/os/linux.py
|
1956
|
+
"""
|
1957
|
+
➜ ~ cat /etc/os-release
|
1958
|
+
NAME="Amazon Linux"
|
1959
|
+
VERSION="2"
|
1960
|
+
ID="amzn"
|
1961
|
+
ID_LIKE="centos rhel fedora"
|
1962
|
+
VERSION_ID="2"
|
1963
|
+
PRETTY_NAME="Amazon Linux 2"
|
1964
|
+
|
1965
|
+
➜ ~ cat /etc/os-release
|
1966
|
+
PRETTY_NAME="Ubuntu 22.04.5 LTS"
|
1967
|
+
NAME="Ubuntu"
|
1968
|
+
VERSION_ID="22.04"
|
1969
|
+
VERSION="22.04.5 LTS (Jammy Jellyfish)"
|
1970
|
+
VERSION_CODENAME=jammy
|
1971
|
+
ID=ubuntu
|
1972
|
+
ID_LIKE=debian
|
1973
|
+
UBUNTU_CODENAME=jammy
|
1974
|
+
|
1975
|
+
➜ omlish git:(master) docker run -i python:3.12 cat /etc/os-release
|
1976
|
+
PRETTY_NAME="Debian GNU/Linux 12 (bookworm)"
|
1977
|
+
NAME="Debian GNU/Linux"
|
1978
|
+
VERSION_ID="12"
|
1979
|
+
VERSION="12 (bookworm)"
|
1980
|
+
VERSION_CODENAME=bookworm
|
1981
|
+
ID=debian
|
1982
|
+
"""
|
1983
|
+
|
1984
|
+
|
1985
|
+
@dc.dataclass(frozen=True)
|
1986
|
+
class LinuxOsRelease:
|
1987
|
+
"""
|
1988
|
+
https://man7.org/linux/man-pages/man5/os-release.5.html
|
1989
|
+
"""
|
1990
|
+
|
1991
|
+
raw: ta.Mapping[str, str]
|
1992
|
+
|
1993
|
+
# General information identifying the operating system
|
1994
|
+
|
1995
|
+
@property
|
1996
|
+
def name(self) -> str:
|
1997
|
+
"""
|
1998
|
+
A string identifying the operating system, without a version component, and suitable for presentation to the
|
1999
|
+
user. If not set, a default of "NAME=Linux" may be used.
|
2000
|
+
|
2001
|
+
Examples: "NAME=Fedora", "NAME="Debian GNU/Linux"".
|
2002
|
+
"""
|
2003
|
+
|
2004
|
+
return self.raw['NAME']
|
2005
|
+
|
2006
|
+
@property
|
2007
|
+
def id(self) -> str:
|
2008
|
+
"""
|
2009
|
+
A lower-case string (no spaces or other characters outside of 0-9, a-z, ".", "_" and "-") identifying the
|
2010
|
+
operating system, excluding any version information and suitable for processing by scripts or usage in generated
|
2011
|
+
filenames. If not set, a default of "ID=linux" may be used. Note that even though this string may not include
|
2012
|
+
characters that require shell quoting, quoting may nevertheless be used.
|
2013
|
+
|
2014
|
+
Examples: "ID=fedora", "ID=debian".
|
2015
|
+
"""
|
2016
|
+
|
2017
|
+
return self.raw['ID']
|
2018
|
+
|
2019
|
+
@property
|
2020
|
+
def id_like(self) -> str:
|
2021
|
+
"""
|
2022
|
+
A space-separated list of operating system identifiers in the same syntax as the ID= setting. It should list
|
2023
|
+
identifiers of operating systems that are closely related to the local operating system in regards to packaging
|
2024
|
+
and programming interfaces, for example listing one or more OS identifiers the local OS is a derivative from. An
|
2025
|
+
OS should generally only list other OS identifiers it itself is a derivative of, and not any OSes that are
|
2026
|
+
derived from it, though symmetric relationships are possible. Build scripts and similar should check this
|
2027
|
+
variable if they need to identify the local operating system and the value of ID= is not recognized. Operating
|
2028
|
+
systems should be listed in order of how closely the local operating system relates to the listed ones, starting
|
2029
|
+
with the closest. This field is optional.
|
2030
|
+
|
2031
|
+
Examples: for an operating system with "ID=centos", an assignment of "ID_LIKE="rhel fedora"" would be
|
2032
|
+
appropriate. For an operating system with "ID=ubuntu", an assignment of "ID_LIKE=debian" is appropriate.
|
2033
|
+
"""
|
2034
|
+
|
2035
|
+
return self.raw['ID_LIKE']
|
2036
|
+
|
2037
|
+
@property
|
2038
|
+
def pretty_name(self) -> str:
|
2039
|
+
"""
|
2040
|
+
A pretty operating system name in a format suitable for presentation to the user. May or may not contain a
|
2041
|
+
release code name or OS version of some kind, as suitable. If not set, a default of "PRETTY_NAME="Linux"" may be
|
2042
|
+
used
|
2043
|
+
|
2044
|
+
Example: "PRETTY_NAME="Fedora 17 (Beefy Miracle)"".
|
2045
|
+
"""
|
2046
|
+
|
2047
|
+
return self.raw['PRETTY_NAME']
|
2048
|
+
|
2049
|
+
@property
|
2050
|
+
def cpe_name(self) -> str:
|
2051
|
+
"""
|
2052
|
+
A CPE name for the operating system, in URI binding syntax, following the Common Platform Enumeration
|
2053
|
+
Specification[4] as proposed by the NIST. This field is optional.
|
2054
|
+
|
2055
|
+
Example: "CPE_NAME="cpe:/o:fedoraproject:fedora:17""
|
2056
|
+
"""
|
2057
|
+
|
2058
|
+
return self.raw['CPE_NAME']
|
2059
|
+
|
2060
|
+
@property
|
2061
|
+
def variant(self) -> str:
|
2062
|
+
"""
|
2063
|
+
A string identifying a specific variant or edition of the operating system suitable for presentation to the
|
2064
|
+
user. This field may be used to inform the user that the configuration of this system is subject to a specific
|
2065
|
+
divergent set of rules or default configuration settings. This field is optional and may not be implemented on
|
2066
|
+
all systems.
|
2067
|
+
|
2068
|
+
Examples: "VARIANT="Server Edition"", "VARIANT="Smart Refrigerator Edition"".
|
2069
|
+
|
2070
|
+
Note: this field is for display purposes only. The VARIANT_ID field should be used for making programmatic
|
2071
|
+
decisions.
|
2072
|
+
|
2073
|
+
Added in version 220.
|
2074
|
+
"""
|
2075
|
+
|
2076
|
+
return self.raw['VARIANT']
|
2077
|
+
|
2078
|
+
@property
|
2079
|
+
def variant_id(self) -> str:
|
2080
|
+
"""
|
2081
|
+
A lower-case string (no spaces or other characters outside of 0-9, a-z, ".", "_" and "-"), identifying a
|
2082
|
+
specific variant or edition of the operating system. This may be interpreted by other packages in order to
|
2083
|
+
determine a divergent default configuration. This field is optional and may not be implemented on all systems.
|
2084
|
+
|
2085
|
+
Examples: "VARIANT_ID=server", "VARIANT_ID=embedded".
|
2086
|
+
|
2087
|
+
Added in version 220.
|
2088
|
+
"""
|
2089
|
+
|
2090
|
+
return self.raw['variant_id']
|
2091
|
+
|
2092
|
+
# Information about the version of the operating system
|
2093
|
+
|
2094
|
+
@property
|
2095
|
+
def version(self) -> str:
|
2096
|
+
"""
|
2097
|
+
A string identifying the operating system version, excluding any OS name information, possibly including a
|
2098
|
+
release code name, and suitable for presentation to the user. This field is optional.
|
2099
|
+
|
2100
|
+
Examples: "VERSION=17", "VERSION="17 (Beefy Miracle)"".
|
2101
|
+
"""
|
2102
|
+
|
2103
|
+
return self.raw['VERSION']
|
2104
|
+
|
2105
|
+
@property
|
2106
|
+
def version_id(self) -> str:
|
2107
|
+
"""
|
2108
|
+
A lower-case string (mostly numeric, no spaces or other characters outside of 0-9, a-z, ".", "_" and "-")
|
2109
|
+
identifying the operating system version, excluding any OS name information or release code name, and suitable
|
2110
|
+
for processing by scripts or usage in generated filenames. This field is optional.
|
2111
|
+
|
2112
|
+
Examples: "VERSION_ID=17", "VERSION_ID=11.04".
|
2113
|
+
"""
|
2114
|
+
|
2115
|
+
return self.raw['VERSION_ID']
|
2116
|
+
|
2117
|
+
@property
|
2118
|
+
def version_codename(self) -> str:
|
2119
|
+
"""
|
2120
|
+
A lower-case string (no spaces or other characters outside of 0-9, a-z, ".", "_" and "-") identifying the
|
2121
|
+
operating system release code name, excluding any OS name information or release version, and suitable for
|
2122
|
+
processing by scripts or usage in generated filenames. This field is optional and may not be implemented on all
|
2123
|
+
systems.
|
2124
|
+
|
2125
|
+
Examples: "VERSION_CODENAME=buster", "VERSION_CODENAME=xenial".
|
2126
|
+
|
2127
|
+
Added in version 231.
|
2128
|
+
"""
|
2129
|
+
|
2130
|
+
return self.raw['VERSION_CODENAME']
|
2131
|
+
|
2132
|
+
@property
|
2133
|
+
def build_id(self) -> str:
|
2134
|
+
"""
|
2135
|
+
A string uniquely identifying the system image originally used as the installation base. In most cases,
|
2136
|
+
VERSION_ID or IMAGE_ID+IMAGE_VERSION are updated when the entire system image is replaced during an update.
|
2137
|
+
BUILD_ID may be used in distributions where the original installation image version is important: VERSION_ID
|
2138
|
+
would change during incremental system updates, but BUILD_ID would not. This field is optional.
|
2139
|
+
|
2140
|
+
Examples: "BUILD_ID="2013-03-20.3"", "BUILD_ID=201303203".
|
2141
|
+
|
2142
|
+
Added in version 200.
|
2143
|
+
"""
|
2144
|
+
|
2145
|
+
return self.raw['BUILD_ID']
|
2146
|
+
|
2147
|
+
@property
|
2148
|
+
def image_id(self) -> str:
|
2149
|
+
"""
|
2150
|
+
A lower-case string (no spaces or other characters outside of 0-9, a-z, ".", "_" and "-"), identifying a
|
2151
|
+
specific image of the operating system. This is supposed to be used for environments where OS images are
|
2152
|
+
prepared, built, shipped and updated as comprehensive, consistent OS images. This field is optional and may not
|
2153
|
+
be implemented on all systems, in particularly not on those that are not managed via images but put together and
|
2154
|
+
updated from individual packages and on the local system.
|
2155
|
+
|
2156
|
+
Examples: "IMAGE_ID=vendorx-cashier-system", "IMAGE_ID=netbook-image".
|
2157
|
+
|
2158
|
+
Added in version 249.
|
2159
|
+
"""
|
2160
|
+
|
2161
|
+
return self.raw['IMAGE_ID']
|
2162
|
+
|
2163
|
+
@property
|
2164
|
+
def image_version(self) -> str:
|
2165
|
+
"""
|
2166
|
+
A lower-case string (mostly numeric, no spaces or other characters outside of 0-9, a-z, ".", "_" and "-")
|
2167
|
+
identifying the OS image version. This is supposed to be used together with IMAGE_ID described above, to discern
|
2168
|
+
different versions of the same image.
|
2169
|
+
|
2170
|
+
Examples: "IMAGE_VERSION=33", "IMAGE_VERSION=47.1rc1".
|
2171
|
+
|
2172
|
+
Added in version 249.
|
2173
|
+
"""
|
2174
|
+
|
2175
|
+
return self.raw['IMAGE_VERSION']
|
2176
|
+
|
2177
|
+
# To summarize: if the image updates are built and shipped as comprehensive units, IMAGE_ID+IMAGE_VERSION is the
|
2178
|
+
# best fit. Otherwise, if updates eventually completely replace previously installed contents, as in a typical
|
2179
|
+
# binary distribution, VERSION_ID should be used to identify major releases of the operating system. BUILD_ID may
|
2180
|
+
# be used instead or in addition to VERSION_ID when the original system image version is important.
|
2181
|
+
|
2182
|
+
#
|
2183
|
+
|
2184
|
+
# Presentation information and links
|
2185
|
+
|
2186
|
+
# Links to resources on the Internet related to the operating system. HOME_URL= should refer to the homepage of the
|
2187
|
+
# operating system, or alternatively some homepage of the specific version of the operating system.
|
2188
|
+
# DOCUMENTATION_URL= should refer to the main documentation page for this operating system. SUPPORT_URL= should
|
2189
|
+
# refer to the main support page for the operating system, if there is any. This is primarily intended for operating
|
2190
|
+
# systems which vendors provide support for. BUG_REPORT_URL= should refer to the main bug reporting page for the
|
2191
|
+
# operating system, if there is any. This is primarily intended for operating systems that rely on community QA.
|
2192
|
+
# PRIVACY_POLICY_URL= should refer to the main privacy policy page for the operating system, if there is any. These
|
2193
|
+
# settings are optional, and providing only some of these settings is common. These URLs are intended to be exposed
|
2194
|
+
# in "About this system" UIs behind links with captions such as "About this Operating System", "Obtain Support",
|
2195
|
+
# "Report a Bug", or "Privacy Policy". The values should be in RFC3986 format[5], and should be "http:" or "https:"
|
2196
|
+
# URLs, and possibly "mailto:" or "tel:". Only one URL shall be listed in each setting. If multiple resources need
|
2197
|
+
# to be referenced, it is recommended to provide an online landing page linking all available resources.
|
2198
|
+
|
2199
|
+
# Examples: "HOME_URL="https://fedoraproject.org/"", "BUG_REPORT_URL="https://bugzilla.redhat.com/"".
|
2200
|
+
|
2201
|
+
@property
|
2202
|
+
def home_url(self) -> str:
|
2203
|
+
return self.raw['HOME_URL']
|
2204
|
+
|
2205
|
+
@property
|
2206
|
+
def documentation_url(self) -> str:
|
2207
|
+
return self.raw['DOCUMENTATION_URL']
|
2208
|
+
|
2209
|
+
@property
|
2210
|
+
def support_url(self) -> str:
|
2211
|
+
return self.raw['SUPPORT_URL']
|
2212
|
+
|
2213
|
+
@property
|
2214
|
+
def bug_report_url(self) -> str:
|
2215
|
+
return self.raw['BUG_REPORT_URL']
|
2216
|
+
|
2217
|
+
@property
|
2218
|
+
def privacy_policy_url(self) -> str:
|
2219
|
+
return self.raw['PRIVACY_POLICY_URL']
|
2220
|
+
|
2221
|
+
@property
|
2222
|
+
def support_end(self) -> str:
|
2223
|
+
"""
|
2224
|
+
The date at which support for this version of the OS ends. (What exactly "lack of support" means varies between
|
2225
|
+
vendors, but generally users should assume that updates, including security fixes, will not be provided.) The
|
2226
|
+
value is a date in the ISO 8601 format "YYYY-MM-DD", and specifies the first day on which support is not
|
2227
|
+
provided.
|
2228
|
+
|
2229
|
+
For example, "SUPPORT_END=2001-01-01" means that the system was supported until the end of the last day of the
|
2230
|
+
previous millennium.
|
2231
|
+
|
2232
|
+
Added in version 252.
|
2233
|
+
"""
|
2234
|
+
|
2235
|
+
return self.raw['SUPPORT_END']
|
2236
|
+
|
2237
|
+
@property
|
2238
|
+
def logo(self) -> str:
|
2239
|
+
"""
|
2240
|
+
A string, specifying the name of an icon as defined by freedesktop.org Icon Theme Specification[6]. This can be
|
2241
|
+
used by graphical applications to display an operating system's or distributor's logo. This field is optional
|
2242
|
+
and may not necessarily be implemented on all systems.
|
2243
|
+
|
2244
|
+
Examples: "LOGO=fedora-logo", "LOGO=distributor-logo-opensuse"
|
2245
|
+
|
2246
|
+
Added in version 240.
|
2247
|
+
"""
|
2248
|
+
|
2249
|
+
return self.raw['LOGO']
|
2250
|
+
|
2251
|
+
@property
|
2252
|
+
def ansi_color(self) -> str:
|
2253
|
+
"""
|
2254
|
+
A suggested presentation color when showing the OS name on the console. This should be specified as string
|
2255
|
+
suitable for inclusion in the ESC [ m ANSI/ECMA-48 escape code for setting graphical rendition. This field is
|
2256
|
+
optional.
|
2257
|
+
|
2258
|
+
Examples: "ANSI_COLOR="0;31"" for red, "ANSI_COLOR="1;34"" for light blue, or "ANSI_COLOR="0;38;2;60;110;180""
|
2259
|
+
for Fedora blue.
|
2260
|
+
"""
|
2261
|
+
|
2262
|
+
return self.raw['ANSI_COLOR']
|
2263
|
+
|
2264
|
+
@property
|
2265
|
+
def vendor_name(self) -> str:
|
2266
|
+
"""
|
2267
|
+
The name of the OS vendor. This is the name of the organization or company which produces the OS. This field is
|
2268
|
+
optional.
|
2269
|
+
|
2270
|
+
This name is intended to be exposed in "About this system" UIs or software update UIs when needed to distinguish
|
2271
|
+
the OS vendor from the OS itself. It is intended to be human readable.
|
2272
|
+
|
2273
|
+
Examples: "VENDOR_NAME="Fedora Project"" for Fedora Linux, "VENDOR_NAME="Canonical"" for Ubuntu.
|
2274
|
+
|
2275
|
+
Added in version 254.
|
2276
|
+
"""
|
2277
|
+
|
2278
|
+
return self.raw['VENDOR_NAME']
|
2279
|
+
|
2280
|
+
@property
|
2281
|
+
def vendor_url(self) -> str:
|
2282
|
+
"""
|
2283
|
+
The homepage of the OS vendor. This field is optional. The VENDOR_NAME= field should be set if this one is,
|
2284
|
+
although clients must be robust against either field not being set.
|
2285
|
+
|
2286
|
+
The value should be in RFC3986 format[5], and should be "http:" or "https:" URLs. Only one URL shall be listed
|
2287
|
+
in the setting.
|
2288
|
+
|
2289
|
+
Examples: "VENDOR_URL="https://fedoraproject.org/"", "VENDOR_URL="https://canonical.com/"".
|
2290
|
+
|
2291
|
+
Added in version 254.
|
2292
|
+
"""
|
2293
|
+
|
2294
|
+
return self.raw['VENDOR_URL']
|
2295
|
+
|
2296
|
+
# Distribution-level defaults and metadata
|
2297
|
+
|
2298
|
+
@property
|
2299
|
+
def default_hostname(self) -> str:
|
2300
|
+
"""
|
2301
|
+
A string specifying the hostname if hostname(5) is not present and no other configuration source specifies the
|
2302
|
+
hostname. Must be either a single DNS label (a string composed of 7-bit ASCII lower-case characters and no
|
2303
|
+
spaces or dots, limited to the format allowed for DNS domain name labels), or a sequence of such labels
|
2304
|
+
separated by single dots that forms a valid DNS FQDN. The hostname must be at most 64 characters, which is a
|
2305
|
+
Linux limitation (DNS allows longer names).
|
2306
|
+
|
2307
|
+
See org.freedesktop.hostname1(5) for a description of how systemd-hostnamed.service(8) determines the fallback
|
2308
|
+
hostname.
|
2309
|
+
|
2310
|
+
Added in version 248.
|
2311
|
+
"""
|
2312
|
+
|
2313
|
+
return self.raw['DEFAULT_HOSTNAME']
|
2314
|
+
|
2315
|
+
@property
|
2316
|
+
def architecture(self) -> str:
|
2317
|
+
"""
|
2318
|
+
A string that specifies which CPU architecture the userspace binaries require. The architecture identifiers are
|
2319
|
+
the same as for ConditionArchitecture= described in systemd.unit(5). The field is optional and should only be
|
2320
|
+
used when just single architecture is supported. It may provide redundant information when used in a GPT
|
2321
|
+
partition with a GUID type that already encodes the architecture. If this is not the case, the architecture
|
2322
|
+
should be specified in e.g., an extension image, to prevent an incompatible host from loading it.
|
2323
|
+
|
2324
|
+
Added in version 252.
|
2325
|
+
"""
|
2326
|
+
|
2327
|
+
return self.raw['ARCHITECTURE']
|
2328
|
+
|
2329
|
+
@property
|
2330
|
+
def sysext_level(self) -> str:
|
2331
|
+
"""
|
2332
|
+
A lower-case string (mostly numeric, no spaces or other characters outside of 0-9, a-z, ".", "_" and "-")
|
2333
|
+
identifying the operating system extensions support level, to indicate which extension images are supported. See
|
2334
|
+
/usr/lib/extension-release.d/extension-release.IMAGE, initrd[2] and systemd-sysext(8)) for more information.
|
2335
|
+
|
2336
|
+
Examples: "SYSEXT_LEVEL=2", "SYSEXT_LEVEL=15.14".
|
2337
|
+
|
2338
|
+
Added in version 248.
|
2339
|
+
"""
|
2340
|
+
|
2341
|
+
return self.raw['SYSEXT_LEVEL']
|
2342
|
+
|
2343
|
+
@property
|
2344
|
+
def confext_level(self) -> str:
|
2345
|
+
"""
|
2346
|
+
Semantically the same as SYSEXT_LEVEL= but for confext images. See
|
2347
|
+
/etc/extension-release.d/extension-release.IMAGE for more information.
|
2348
|
+
|
2349
|
+
Examples: "CONFEXT_LEVEL=2", "CONFEXT_LEVEL=15.14".
|
2350
|
+
|
2351
|
+
Added in version 254.
|
2352
|
+
"""
|
2353
|
+
|
2354
|
+
return self.raw['CONFEXT_LEVEL']
|
2355
|
+
|
2356
|
+
@property
|
2357
|
+
def sysext_scope(self) -> str:
|
2358
|
+
"""
|
2359
|
+
Takes a space-separated list of one or more of the strings "system", "initrd" and "portable". This field is only
|
2360
|
+
supported in extension-release.d/ files and indicates what environments the system extension is applicable to:
|
2361
|
+
i.e. to regular systems, to initrds, or to portable service images. If unspecified, "SYSEXT_SCOPE=system
|
2362
|
+
portable" is implied, i.e. any system extension without this field is applicable to regular systems and to
|
2363
|
+
portable service environments, but not to initrd environments.
|
2364
|
+
|
2365
|
+
Added in version 250.
|
2366
|
+
"""
|
2367
|
+
|
2368
|
+
return self.raw['SYSEXT_SCOPE']
|
2369
|
+
|
2370
|
+
@property
|
2371
|
+
def confext_scope(self) -> str:
|
2372
|
+
"""
|
2373
|
+
Semantically the same as SYSEXT_SCOPE= but for confext images.
|
2374
|
+
|
2375
|
+
Added in version 254.
|
2376
|
+
"""
|
2377
|
+
|
2378
|
+
return self.raw['CONFEXT_SCOPE']
|
2379
|
+
|
2380
|
+
@property
|
2381
|
+
def portable_prefixes(self) -> str:
|
2382
|
+
"""
|
2383
|
+
Takes a space-separated list of one or more valid prefix match strings for the Portable Services[3] logic. This
|
2384
|
+
field serves two purposes: it is informational, identifying portable service images as such (and thus allowing
|
2385
|
+
them to be distinguished from other OS images, such as bootable system images). It is also used when a portable
|
2386
|
+
service image is attached: the specified or implied portable service prefix is checked against the list
|
2387
|
+
specified here, to enforce restrictions how images may be attached to a system.
|
2388
|
+
|
2389
|
+
Added in version 250.
|
2390
|
+
"""
|
2391
|
+
|
2392
|
+
return self.raw['PORTABLE_PREFIXES']
|
2393
|
+
|
2394
|
+
#
|
2395
|
+
|
2396
|
+
DEFAULT_PATHS: ta.ClassVar[ta.Sequence[str]] = [
|
2397
|
+
'/etc/os-release',
|
2398
|
+
'/usr/lib/os-release',
|
2399
|
+
]
|
2400
|
+
|
2401
|
+
@classmethod
|
2402
|
+
def read(cls, *paths: str) -> ta.Optional['LinuxOsRelease']:
|
2403
|
+
for fp in (paths or cls.DEFAULT_PATHS):
|
2404
|
+
if not os.path.isfile(fp):
|
2405
|
+
continue
|
2406
|
+
with open(fp) as f:
|
2407
|
+
src = f.read()
|
2408
|
+
break
|
2409
|
+
else:
|
2410
|
+
return None
|
2411
|
+
|
2412
|
+
raw = cls._parse_os_release(src)
|
2413
|
+
|
2414
|
+
return cls(raw)
|
2415
|
+
|
2416
|
+
@classmethod
|
2417
|
+
def _parse_os_release(cls, src: str) -> ta.Mapping[str, str]:
|
2418
|
+
dct: ta.Dict[str, str] = {}
|
2419
|
+
|
2420
|
+
for l in src.splitlines():
|
2421
|
+
if not (l := l.strip()):
|
2422
|
+
continue
|
2423
|
+
if l.startswith('#') or '=' not in l:
|
2424
|
+
continue
|
2425
|
+
|
2426
|
+
k, _, v = l.partition('=')
|
2427
|
+
if k.startswith('"'):
|
2428
|
+
k = k[1:-1]
|
2429
|
+
if v.startswith('"'):
|
2430
|
+
v = v[1:-1]
|
2431
|
+
|
2432
|
+
dct[k] = v
|
2433
|
+
|
2434
|
+
return dct
|
2435
|
+
|
2436
|
+
|
1940
2437
|
########################################
|
1941
2438
|
# ../../../omdev/packaging/specifiers.py
|
1942
2439
|
# Copyright (c) Donald Stufft and individual contributors.
|
@@ -2610,43 +3107,264 @@ def build_command_name_map(crs: CommandRegistrations) -> CommandNameMap:
|
|
2610
3107
|
|
2611
3108
|
|
2612
3109
|
########################################
|
2613
|
-
# ../
|
3110
|
+
# ../deploy/paths.py
|
3111
|
+
"""
|
3112
|
+
~deploy
|
3113
|
+
deploy.pid (flock)
|
3114
|
+
/app
|
3115
|
+
/<appspec> - shallow clone
|
3116
|
+
/conf
|
3117
|
+
/env
|
3118
|
+
<appspec>.env
|
3119
|
+
/nginx
|
3120
|
+
<appspec>.conf
|
3121
|
+
/supervisor
|
3122
|
+
<appspec>.conf
|
3123
|
+
/venv
|
3124
|
+
/<appspec>
|
3125
|
+
|
3126
|
+
?
|
3127
|
+
/logs
|
3128
|
+
/wrmsr--omlish--<spec>
|
3129
|
+
|
3130
|
+
spec = <name>--<rev>--<when>
|
3131
|
+
|
3132
|
+
==
|
3133
|
+
|
3134
|
+
for dn in [
|
3135
|
+
'app',
|
3136
|
+
'conf',
|
3137
|
+
'conf/env',
|
3138
|
+
'conf/nginx',
|
3139
|
+
'conf/supervisor',
|
3140
|
+
'venv',
|
3141
|
+
]:
|
3142
|
+
|
3143
|
+
==
|
2614
3144
|
|
3145
|
+
"""
|
2615
3146
|
|
2616
|
-
@dc.dataclass(frozen=True)
|
2617
|
-
class RemoteConfig:
|
2618
|
-
payload_file: ta.Optional[str] = None
|
2619
3147
|
|
2620
|
-
|
3148
|
+
##
|
2621
3149
|
|
2622
|
-
deathsig: ta.Optional[str] = 'KILL'
|
2623
3150
|
|
2624
|
-
|
3151
|
+
DEPLOY_PATH_SPEC_PLACEHOLDER = '@'
|
3152
|
+
DEPLOY_PATH_SPEC_SEPARATORS = '-.'
|
2625
3153
|
|
2626
|
-
|
3154
|
+
DEPLOY_PATH_SPECS: ta.FrozenSet[str] = frozenset([
|
3155
|
+
'app',
|
3156
|
+
'tag', # <rev>-<dt>
|
3157
|
+
])
|
2627
3158
|
|
2628
|
-
timebomb_delay_s: ta.Optional[float] = 60 * 60.
|
2629
3159
|
|
2630
|
-
|
3160
|
+
class DeployPathError(Exception):
|
3161
|
+
pass
|
2631
3162
|
|
2632
|
-
use_in_process_remote_executor: bool = False
|
2633
3163
|
|
3164
|
+
@dc.dataclass(frozen=True)
|
3165
|
+
class DeployPathPart(abc.ABC): # noqa
|
3166
|
+
@property
|
3167
|
+
@abc.abstractmethod
|
3168
|
+
def kind(self) -> DeployPathKind:
|
3169
|
+
raise NotImplementedError
|
2634
3170
|
|
2635
|
-
|
2636
|
-
|
3171
|
+
@abc.abstractmethod
|
3172
|
+
def render(self, specs: ta.Optional[ta.Mapping[DeployPathSpec, str]] = None) -> str:
|
3173
|
+
raise NotImplementedError
|
2637
3174
|
|
2638
3175
|
|
2639
|
-
|
3176
|
+
#
|
2640
3177
|
|
2641
3178
|
|
2642
|
-
|
2643
|
-
|
2644
|
-
|
3179
|
+
class DirDeployPathPart(DeployPathPart, abc.ABC):
|
3180
|
+
@property
|
3181
|
+
def kind(self) -> DeployPathKind:
|
3182
|
+
return 'dir'
|
2645
3183
|
|
3184
|
+
@classmethod
|
3185
|
+
def parse(cls, s: str) -> 'DirDeployPathPart':
|
3186
|
+
if DEPLOY_PATH_SPEC_PLACEHOLDER in s:
|
3187
|
+
check.equal(s[0], DEPLOY_PATH_SPEC_PLACEHOLDER)
|
3188
|
+
return SpecDirDeployPathPart(s[1:])
|
3189
|
+
else:
|
3190
|
+
return ConstDirDeployPathPart(s)
|
2646
3191
|
|
2647
|
-
|
2648
|
-
|
2649
|
-
|
3192
|
+
|
3193
|
+
class FileDeployPathPart(DeployPathPart, abc.ABC):
|
3194
|
+
@property
|
3195
|
+
def kind(self) -> DeployPathKind:
|
3196
|
+
return 'file'
|
3197
|
+
|
3198
|
+
@classmethod
|
3199
|
+
def parse(cls, s: str) -> 'FileDeployPathPart':
|
3200
|
+
if DEPLOY_PATH_SPEC_PLACEHOLDER in s:
|
3201
|
+
check.equal(s[0], DEPLOY_PATH_SPEC_PLACEHOLDER)
|
3202
|
+
if not any(c in s for c in DEPLOY_PATH_SPEC_SEPARATORS):
|
3203
|
+
return SpecFileDeployPathPart(s[1:], '')
|
3204
|
+
else:
|
3205
|
+
p = min(f for c in DEPLOY_PATH_SPEC_SEPARATORS if (f := s.find(c)) > 0)
|
3206
|
+
return SpecFileDeployPathPart(s[1:p], s[p:])
|
3207
|
+
else:
|
3208
|
+
return ConstFileDeployPathPart(s)
|
3209
|
+
|
3210
|
+
|
3211
|
+
#
|
3212
|
+
|
3213
|
+
|
3214
|
+
@dc.dataclass(frozen=True)
|
3215
|
+
class ConstDeployPathPart(DeployPathPart, abc.ABC):
|
3216
|
+
name: str
|
3217
|
+
|
3218
|
+
def __post_init__(self) -> None:
|
3219
|
+
check.non_empty_str(self.name)
|
3220
|
+
check.not_in('/', self.name)
|
3221
|
+
check.not_in(DEPLOY_PATH_SPEC_PLACEHOLDER, self.name)
|
3222
|
+
|
3223
|
+
def render(self, specs: ta.Optional[ta.Mapping[DeployPathSpec, str]] = None) -> str:
|
3224
|
+
return self.name
|
3225
|
+
|
3226
|
+
|
3227
|
+
class ConstDirDeployPathPart(ConstDeployPathPart, DirDeployPathPart):
|
3228
|
+
pass
|
3229
|
+
|
3230
|
+
|
3231
|
+
class ConstFileDeployPathPart(ConstDeployPathPart, FileDeployPathPart):
|
3232
|
+
pass
|
3233
|
+
|
3234
|
+
|
3235
|
+
#
|
3236
|
+
|
3237
|
+
|
3238
|
+
@dc.dataclass(frozen=True)
|
3239
|
+
class SpecDeployPathPart(DeployPathPart, abc.ABC):
|
3240
|
+
spec: str # DeployPathSpec
|
3241
|
+
|
3242
|
+
def __post_init__(self) -> None:
|
3243
|
+
check.non_empty_str(self.spec)
|
3244
|
+
for c in [*DEPLOY_PATH_SPEC_SEPARATORS, DEPLOY_PATH_SPEC_PLACEHOLDER, '/']:
|
3245
|
+
check.not_in(c, self.spec)
|
3246
|
+
check.in_(self.spec, DEPLOY_PATH_SPECS)
|
3247
|
+
|
3248
|
+
def _render_spec(self, specs: ta.Optional[ta.Mapping[DeployPathSpec, str]] = None) -> str:
|
3249
|
+
if specs is not None:
|
3250
|
+
return specs[self.spec] # type: ignore
|
3251
|
+
else:
|
3252
|
+
return DEPLOY_PATH_SPEC_PLACEHOLDER + self.spec
|
3253
|
+
|
3254
|
+
|
3255
|
+
@dc.dataclass(frozen=True)
|
3256
|
+
class SpecDirDeployPathPart(SpecDeployPathPart, DirDeployPathPart):
|
3257
|
+
def render(self, specs: ta.Optional[ta.Mapping[DeployPathSpec, str]] = None) -> str:
|
3258
|
+
return self._render_spec(specs)
|
3259
|
+
|
3260
|
+
|
3261
|
+
@dc.dataclass(frozen=True)
|
3262
|
+
class SpecFileDeployPathPart(SpecDeployPathPart, FileDeployPathPart):
|
3263
|
+
suffix: str
|
3264
|
+
|
3265
|
+
def __post_init__(self) -> None:
|
3266
|
+
super().__post_init__()
|
3267
|
+
if self.suffix:
|
3268
|
+
for c in [DEPLOY_PATH_SPEC_PLACEHOLDER, '/']:
|
3269
|
+
check.not_in(c, self.suffix)
|
3270
|
+
|
3271
|
+
def render(self, specs: ta.Optional[ta.Mapping[DeployPathSpec, str]] = None) -> str:
|
3272
|
+
return self._render_spec(specs) + self.suffix
|
3273
|
+
|
3274
|
+
|
3275
|
+
##
|
3276
|
+
|
3277
|
+
|
3278
|
+
@dc.dataclass(frozen=True)
|
3279
|
+
class DeployPath:
|
3280
|
+
parts: ta.Sequence[DeployPathPart]
|
3281
|
+
|
3282
|
+
def __post_init__(self) -> None:
|
3283
|
+
check.not_empty(self.parts)
|
3284
|
+
for p in self.parts[:-1]:
|
3285
|
+
check.equal(p.kind, 'dir')
|
3286
|
+
|
3287
|
+
pd = {}
|
3288
|
+
for i, p in enumerate(self.parts):
|
3289
|
+
if isinstance(p, SpecDeployPathPart):
|
3290
|
+
if p.spec in pd:
|
3291
|
+
raise DeployPathError('Duplicate specs in path', self)
|
3292
|
+
pd[p.spec] = i
|
3293
|
+
|
3294
|
+
if 'tag' in pd:
|
3295
|
+
if 'app' not in pd or pd['app'] >= pd['tag']:
|
3296
|
+
raise DeployPathError('Tag spec in path without preceding app', self)
|
3297
|
+
|
3298
|
+
@property
|
3299
|
+
def kind(self) -> ta.Literal['file', 'dir']:
|
3300
|
+
return self.parts[-1].kind
|
3301
|
+
|
3302
|
+
def render(self, specs: ta.Optional[ta.Mapping[DeployPathSpec, str]] = None) -> str:
|
3303
|
+
return os.path.join( # noqa
|
3304
|
+
*[p.render(specs) for p in self.parts],
|
3305
|
+
*([''] if self.kind == 'dir' else []),
|
3306
|
+
)
|
3307
|
+
|
3308
|
+
@classmethod
|
3309
|
+
def parse(cls, s: str) -> 'DeployPath':
|
3310
|
+
tail_parse: ta.Callable[[str], DeployPathPart]
|
3311
|
+
if s.endswith('/'):
|
3312
|
+
tail_parse = DirDeployPathPart.parse
|
3313
|
+
s = s[:-1]
|
3314
|
+
else:
|
3315
|
+
tail_parse = FileDeployPathPart.parse
|
3316
|
+
ps = check.non_empty_str(s).split('/')
|
3317
|
+
return cls([
|
3318
|
+
*([DirDeployPathPart.parse(p) for p in ps[:-1]] if len(ps) > 1 else []),
|
3319
|
+
tail_parse(ps[-1]),
|
3320
|
+
])
|
3321
|
+
|
3322
|
+
|
3323
|
+
##
|
3324
|
+
|
3325
|
+
|
3326
|
+
class DeployPathOwner(abc.ABC):
|
3327
|
+
@abc.abstractmethod
|
3328
|
+
def get_deploy_paths(self) -> ta.AbstractSet[DeployPath]:
|
3329
|
+
raise NotImplementedError
|
3330
|
+
|
3331
|
+
|
3332
|
+
########################################
|
3333
|
+
# ../remote/config.py
|
3334
|
+
|
3335
|
+
|
3336
|
+
@dc.dataclass(frozen=True)
|
3337
|
+
class RemoteConfig:
|
3338
|
+
payload_file: ta.Optional[str] = None
|
3339
|
+
|
3340
|
+
set_pgid: bool = True
|
3341
|
+
|
3342
|
+
deathsig: ta.Optional[str] = 'KILL'
|
3343
|
+
|
3344
|
+
pycharm_remote_debug: ta.Optional[PycharmRemoteDebug] = None
|
3345
|
+
|
3346
|
+
forward_logging: bool = True
|
3347
|
+
|
3348
|
+
timebomb_delay_s: ta.Optional[float] = 60 * 60.
|
3349
|
+
|
3350
|
+
heartbeat_interval_s: float = 3.
|
3351
|
+
|
3352
|
+
|
3353
|
+
########################################
|
3354
|
+
# ../remote/payload.py
|
3355
|
+
|
3356
|
+
|
3357
|
+
RemoteExecutionPayloadFile = ta.NewType('RemoteExecutionPayloadFile', str)
|
3358
|
+
|
3359
|
+
|
3360
|
+
@cached_nullary
|
3361
|
+
def _get_self_src() -> str:
|
3362
|
+
return inspect.getsource(sys.modules[__name__])
|
3363
|
+
|
3364
|
+
|
3365
|
+
def _is_src_amalg(src: str) -> bool:
|
3366
|
+
for l in src.splitlines(): # noqa
|
3367
|
+
if l.startswith('# @omlish-amalg-output '):
|
2650
3368
|
return True
|
2651
3369
|
return False
|
2652
3370
|
|
@@ -2671,6 +3389,90 @@ def get_remote_payload_src(
|
|
2671
3389
|
return importlib.resources.files(__package__.split('.')[0] + '.scripts').joinpath('manage.py').read_text()
|
2672
3390
|
|
2673
3391
|
|
3392
|
+
########################################
|
3393
|
+
# ../targets/targets.py
|
3394
|
+
"""
|
3395
|
+
It's desugaring. Subprocess and locals are only leafs. Retain an origin?
|
3396
|
+
** TWO LAYERS ** - ManageTarget is user-facing, ConnectorTarget is bound, internal
|
3397
|
+
"""
|
3398
|
+
|
3399
|
+
|
3400
|
+
##
|
3401
|
+
|
3402
|
+
|
3403
|
+
class ManageTarget(abc.ABC): # noqa
|
3404
|
+
def __init_subclass__(cls, **kwargs: ta.Any) -> None:
|
3405
|
+
super().__init_subclass__(**kwargs)
|
3406
|
+
|
3407
|
+
check.state(cls.__name__.endswith('ManageTarget'))
|
3408
|
+
|
3409
|
+
|
3410
|
+
#
|
3411
|
+
|
3412
|
+
|
3413
|
+
@dc.dataclass(frozen=True)
|
3414
|
+
class PythonRemoteManageTarget:
|
3415
|
+
DEFAULT_PYTHON: ta.ClassVar[str] = 'python3'
|
3416
|
+
python: str = DEFAULT_PYTHON
|
3417
|
+
|
3418
|
+
|
3419
|
+
#
|
3420
|
+
|
3421
|
+
|
3422
|
+
class RemoteManageTarget(ManageTarget, abc.ABC):
|
3423
|
+
pass
|
3424
|
+
|
3425
|
+
|
3426
|
+
class PhysicallyRemoteManageTarget(RemoteManageTarget, abc.ABC):
|
3427
|
+
pass
|
3428
|
+
|
3429
|
+
|
3430
|
+
class LocalManageTarget(ManageTarget, abc.ABC):
|
3431
|
+
pass
|
3432
|
+
|
3433
|
+
|
3434
|
+
##
|
3435
|
+
|
3436
|
+
|
3437
|
+
@dc.dataclass(frozen=True)
|
3438
|
+
class SshManageTarget(PhysicallyRemoteManageTarget, PythonRemoteManageTarget):
|
3439
|
+
host: ta.Optional[str] = None
|
3440
|
+
username: ta.Optional[str] = None
|
3441
|
+
key_file: ta.Optional[str] = None
|
3442
|
+
|
3443
|
+
def __post_init__(self) -> None:
|
3444
|
+
check.non_empty_str(self.host)
|
3445
|
+
|
3446
|
+
|
3447
|
+
##
|
3448
|
+
|
3449
|
+
|
3450
|
+
@dc.dataclass(frozen=True)
|
3451
|
+
class DockerManageTarget(RemoteManageTarget, PythonRemoteManageTarget): # noqa
|
3452
|
+
image: ta.Optional[str] = None
|
3453
|
+
container_id: ta.Optional[str] = None
|
3454
|
+
|
3455
|
+
def __post_init__(self) -> None:
|
3456
|
+
check.arg(bool(self.image) ^ bool(self.container_id))
|
3457
|
+
|
3458
|
+
|
3459
|
+
##
|
3460
|
+
|
3461
|
+
|
3462
|
+
@dc.dataclass(frozen=True)
|
3463
|
+
class InProcessManageTarget(LocalManageTarget):
|
3464
|
+
class Mode(enum.Enum):
|
3465
|
+
DIRECT = enum.auto()
|
3466
|
+
FAKE_REMOTE = enum.auto()
|
3467
|
+
|
3468
|
+
mode: Mode = Mode.DIRECT
|
3469
|
+
|
3470
|
+
|
3471
|
+
@dc.dataclass(frozen=True)
|
3472
|
+
class SubprocessManageTarget(LocalManageTarget, PythonRemoteManageTarget):
|
3473
|
+
pass
|
3474
|
+
|
3475
|
+
|
2674
3476
|
########################################
|
2675
3477
|
# ../../../omlish/argparse/cli.py
|
2676
3478
|
"""
|
@@ -2943,6 +3745,78 @@ class ArgparseCli:
|
|
2943
3745
|
return await fn()
|
2944
3746
|
|
2945
3747
|
|
3748
|
+
########################################
|
3749
|
+
# ../../../omlish/lite/contextmanagers.py
|
3750
|
+
|
3751
|
+
|
3752
|
+
##
|
3753
|
+
|
3754
|
+
|
3755
|
+
class ExitStacked:
|
3756
|
+
_exit_stack: ta.Optional[contextlib.ExitStack] = None
|
3757
|
+
|
3758
|
+
def __enter__(self: ExitStackedT) -> ExitStackedT:
|
3759
|
+
check.state(self._exit_stack is None)
|
3760
|
+
es = self._exit_stack = contextlib.ExitStack()
|
3761
|
+
es.__enter__()
|
3762
|
+
return self
|
3763
|
+
|
3764
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
3765
|
+
if (es := self._exit_stack) is None:
|
3766
|
+
return None
|
3767
|
+
self._exit_contexts()
|
3768
|
+
return es.__exit__(exc_type, exc_val, exc_tb)
|
3769
|
+
|
3770
|
+
def _exit_contexts(self) -> None:
|
3771
|
+
pass
|
3772
|
+
|
3773
|
+
def _enter_context(self, cm: ta.ContextManager[T]) -> T:
|
3774
|
+
es = check.not_none(self._exit_stack)
|
3775
|
+
return es.enter_context(cm)
|
3776
|
+
|
3777
|
+
|
3778
|
+
##
|
3779
|
+
|
3780
|
+
|
3781
|
+
@contextlib.contextmanager
|
3782
|
+
def defer(fn: ta.Callable) -> ta.Generator[ta.Callable, None, None]:
|
3783
|
+
try:
|
3784
|
+
yield fn
|
3785
|
+
finally:
|
3786
|
+
fn()
|
3787
|
+
|
3788
|
+
|
3789
|
+
@contextlib.contextmanager
|
3790
|
+
def attr_setting(obj, attr, val, *, default=None): # noqa
|
3791
|
+
not_set = object()
|
3792
|
+
orig = getattr(obj, attr, not_set)
|
3793
|
+
try:
|
3794
|
+
setattr(obj, attr, val)
|
3795
|
+
if orig is not not_set:
|
3796
|
+
yield orig
|
3797
|
+
else:
|
3798
|
+
yield default
|
3799
|
+
finally:
|
3800
|
+
if orig is not_set:
|
3801
|
+
delattr(obj, attr)
|
3802
|
+
else:
|
3803
|
+
setattr(obj, attr, orig)
|
3804
|
+
|
3805
|
+
|
3806
|
+
##
|
3807
|
+
|
3808
|
+
|
3809
|
+
class aclosing(contextlib.AbstractAsyncContextManager): # noqa
|
3810
|
+
def __init__(self, thing):
|
3811
|
+
self.thing = thing
|
3812
|
+
|
3813
|
+
async def __aenter__(self):
|
3814
|
+
return self.thing
|
3815
|
+
|
3816
|
+
async def __aexit__(self, *exc_info):
|
3817
|
+
await self.thing.aclose()
|
3818
|
+
|
3819
|
+
|
2946
3820
|
########################################
|
2947
3821
|
# ../../../omlish/lite/inject.py
|
2948
3822
|
|
@@ -4111,7 +4985,8 @@ def configure_standard_logging(
|
|
4111
4985
|
"""
|
4112
4986
|
TODO:
|
4113
4987
|
- pickle stdlib objs? have to pin to 3.8 pickle protocol, will be cross-version
|
4114
|
-
-
|
4988
|
+
- namedtuple
|
4989
|
+
- literals
|
4115
4990
|
"""
|
4116
4991
|
|
4117
4992
|
|
@@ -4401,14 +5276,18 @@ class ObjMarshalerManager:
|
|
4401
5276
|
) -> ObjMarshaler:
|
4402
5277
|
if isinstance(ty, type):
|
4403
5278
|
if abc.ABC in ty.__bases__:
|
4404
|
-
|
5279
|
+
impls = [ity for ity in deep_subclasses(ty) if abc.ABC not in ity.__bases__] # type: ignore
|
5280
|
+
if all(ity.__qualname__.endswith(ty.__name__) for ity in impls):
|
5281
|
+
ins = {ity: snake_case(ity.__qualname__[:-len(ty.__name__)]) for ity in impls}
|
5282
|
+
else:
|
5283
|
+
ins = {ity: ity.__qualname__ for ity in impls}
|
5284
|
+
return PolymorphicObjMarshaler.of([
|
4405
5285
|
PolymorphicObjMarshaler.Impl(
|
4406
5286
|
ity,
|
4407
|
-
|
5287
|
+
itn,
|
4408
5288
|
rec(ity),
|
4409
5289
|
)
|
4410
|
-
for ity in
|
4411
|
-
if abc.ABC not in ity.__bases__
|
5290
|
+
for ity, itn in ins.items()
|
4412
5291
|
])
|
4413
5292
|
|
4414
5293
|
if issubclass(ty, enum.Enum):
|
@@ -4655,41 +5534,6 @@ class Interp:
|
|
4655
5534
|
version: InterpVersion
|
4656
5535
|
|
4657
5536
|
|
4658
|
-
########################################
|
4659
|
-
# ../bootstrap.py
|
4660
|
-
|
4661
|
-
|
4662
|
-
@dc.dataclass(frozen=True)
|
4663
|
-
class MainBootstrap:
|
4664
|
-
main_config: MainConfig = MainConfig()
|
4665
|
-
|
4666
|
-
remote_config: RemoteConfig = RemoteConfig()
|
4667
|
-
|
4668
|
-
system_config: SystemConfig = SystemConfig()
|
4669
|
-
|
4670
|
-
|
4671
|
-
########################################
|
4672
|
-
# ../commands/execution.py
|
4673
|
-
|
4674
|
-
|
4675
|
-
CommandExecutorMap = ta.NewType('CommandExecutorMap', ta.Mapping[ta.Type[Command], CommandExecutor])
|
4676
|
-
|
4677
|
-
|
4678
|
-
class LocalCommandExecutor(CommandExecutor):
|
4679
|
-
def __init__(
|
4680
|
-
self,
|
4681
|
-
*,
|
4682
|
-
command_executors: CommandExecutorMap,
|
4683
|
-
) -> None:
|
4684
|
-
super().__init__()
|
4685
|
-
|
4686
|
-
self._command_executors = command_executors
|
4687
|
-
|
4688
|
-
async def execute(self, cmd: Command) -> Command.Output:
|
4689
|
-
ce: CommandExecutor = self._command_executors[type(cmd)]
|
4690
|
-
return await ce.execute(cmd)
|
4691
|
-
|
4692
|
-
|
4693
5537
|
########################################
|
4694
5538
|
# ../commands/marshal.py
|
4695
5539
|
|
@@ -4715,6 +5559,34 @@ def install_command_marshaling(
|
|
4715
5559
|
)
|
4716
5560
|
|
4717
5561
|
|
5562
|
+
########################################
|
5563
|
+
# ../commands/ping.py
|
5564
|
+
|
5565
|
+
|
5566
|
+
##
|
5567
|
+
|
5568
|
+
|
5569
|
+
@dc.dataclass(frozen=True)
|
5570
|
+
class PingCommand(Command['PingCommand.Output']):
|
5571
|
+
time: float = dc.field(default_factory=time.time)
|
5572
|
+
|
5573
|
+
@dc.dataclass(frozen=True)
|
5574
|
+
class Output(Command.Output):
|
5575
|
+
time: float
|
5576
|
+
|
5577
|
+
|
5578
|
+
class PingCommandExecutor(CommandExecutor[PingCommand, PingCommand.Output]):
|
5579
|
+
async def execute(self, cmd: PingCommand) -> PingCommand.Output:
|
5580
|
+
return PingCommand.Output(cmd.time)
|
5581
|
+
|
5582
|
+
|
5583
|
+
########################################
|
5584
|
+
# ../commands/types.py
|
5585
|
+
|
5586
|
+
|
5587
|
+
CommandExecutorMap = ta.NewType('CommandExecutorMap', ta.Mapping[ta.Type[Command], CommandExecutor])
|
5588
|
+
|
5589
|
+
|
4718
5590
|
########################################
|
4719
5591
|
# ../deploy/commands.py
|
4720
5592
|
|
@@ -4828,24 +5700,72 @@ class RemoteChannelImpl(RemoteChannel):
|
|
4828
5700
|
|
4829
5701
|
|
4830
5702
|
########################################
|
4831
|
-
# ../system/
|
5703
|
+
# ../system/platforms.py
|
4832
5704
|
|
4833
5705
|
|
4834
5706
|
##
|
4835
5707
|
|
4836
5708
|
|
4837
5709
|
@dc.dataclass(frozen=True)
|
4838
|
-
class
|
4839
|
-
|
4840
|
-
class Output(Command.Output):
|
4841
|
-
pass
|
5710
|
+
class Platform(abc.ABC): # noqa
|
5711
|
+
pass
|
4842
5712
|
|
4843
5713
|
|
4844
|
-
class
|
4845
|
-
|
4846
|
-
|
5714
|
+
class LinuxPlatform(Platform, abc.ABC):
|
5715
|
+
pass
|
5716
|
+
|
5717
|
+
|
5718
|
+
class UbuntuPlatform(LinuxPlatform):
|
5719
|
+
pass
|
5720
|
+
|
5721
|
+
|
5722
|
+
class AmazonLinuxPlatform(LinuxPlatform):
|
5723
|
+
pass
|
5724
|
+
|
5725
|
+
|
5726
|
+
class GenericLinuxPlatform(LinuxPlatform):
|
5727
|
+
pass
|
5728
|
+
|
5729
|
+
|
5730
|
+
class DarwinPlatform(Platform):
|
5731
|
+
pass
|
5732
|
+
|
5733
|
+
|
5734
|
+
class UnknownPlatform(Platform):
|
5735
|
+
pass
|
5736
|
+
|
5737
|
+
|
5738
|
+
##
|
5739
|
+
|
5740
|
+
|
5741
|
+
def _detect_system_platform() -> Platform:
|
5742
|
+
plat = sys.platform
|
5743
|
+
|
5744
|
+
if plat == 'linux':
|
5745
|
+
if (osr := LinuxOsRelease.read()) is None:
|
5746
|
+
return GenericLinuxPlatform()
|
5747
|
+
|
5748
|
+
if osr.id == 'amzn':
|
5749
|
+
return AmazonLinuxPlatform()
|
5750
|
+
|
5751
|
+
elif osr.id == 'ubuntu':
|
5752
|
+
return UbuntuPlatform()
|
5753
|
+
|
5754
|
+
else:
|
5755
|
+
return GenericLinuxPlatform()
|
4847
5756
|
|
4848
|
-
|
5757
|
+
elif plat == 'darwin':
|
5758
|
+
return DarwinPlatform()
|
5759
|
+
|
5760
|
+
else:
|
5761
|
+
return UnknownPlatform()
|
5762
|
+
|
5763
|
+
|
5764
|
+
@cached_nullary
|
5765
|
+
def detect_system_platform() -> Platform:
|
5766
|
+
platform = _detect_system_platform()
|
5767
|
+
log.info('Detected platform: %r', platform)
|
5768
|
+
return platform
|
4849
5769
|
|
4850
5770
|
|
4851
5771
|
########################################
|
@@ -5032,6 +5952,25 @@ def subprocess_close(
|
|
5032
5952
|
proc.wait(timeout)
|
5033
5953
|
|
5034
5954
|
|
5955
|
+
########################################
|
5956
|
+
# ../commands/local.py
|
5957
|
+
|
5958
|
+
|
5959
|
+
class LocalCommandExecutor(CommandExecutor):
|
5960
|
+
def __init__(
|
5961
|
+
self,
|
5962
|
+
*,
|
5963
|
+
command_executors: CommandExecutorMap,
|
5964
|
+
) -> None:
|
5965
|
+
super().__init__()
|
5966
|
+
|
5967
|
+
self._command_executors = command_executors
|
5968
|
+
|
5969
|
+
async def execute(self, cmd: Command) -> Command.Output:
|
5970
|
+
ce: CommandExecutor = self._command_executors[type(cmd)]
|
5971
|
+
return await ce.execute(cmd)
|
5972
|
+
|
5973
|
+
|
5035
5974
|
########################################
|
5036
5975
|
# ../remote/execution.py
|
5037
5976
|
"""
|
@@ -5409,7 +6348,7 @@ class RemoteCommandExecutor(CommandExecutor):
|
|
5409
6348
|
self,
|
5410
6349
|
cmd: Command,
|
5411
6350
|
*,
|
5412
|
-
log: ta.Optional[logging.Logger] = None,
|
6351
|
+
log: ta.Optional[logging.Logger] = None, # noqa
|
5413
6352
|
omit_exc_object: bool = False,
|
5414
6353
|
) -> CommandOutputOrException:
|
5415
6354
|
try:
|
@@ -5429,6 +6368,15 @@ class RemoteCommandExecutor(CommandExecutor):
|
|
5429
6368
|
return r
|
5430
6369
|
|
5431
6370
|
|
6371
|
+
########################################
|
6372
|
+
# ../system/config.py
|
6373
|
+
|
6374
|
+
|
6375
|
+
@dc.dataclass(frozen=True)
|
6376
|
+
class SystemConfig:
|
6377
|
+
platform: ta.Optional[Platform] = None
|
6378
|
+
|
6379
|
+
|
5432
6380
|
########################################
|
5433
6381
|
# ../../../omlish/lite/asyncio/subprocesses.py
|
5434
6382
|
|
@@ -5591,6 +6539,13 @@ async def asyncio_subprocess_communicate(
|
|
5591
6539
|
return await AsyncioProcessCommunicator(proc).communicate(input, timeout) # noqa
|
5592
6540
|
|
5593
6541
|
|
6542
|
+
@dc.dataclass(frozen=True)
|
6543
|
+
class AsyncioSubprocessOutput:
|
6544
|
+
proc: asyncio.subprocess.Process
|
6545
|
+
stdout: ta.Optional[bytes]
|
6546
|
+
stderr: ta.Optional[bytes]
|
6547
|
+
|
6548
|
+
|
5594
6549
|
async def asyncio_subprocess_run(
|
5595
6550
|
*args: str,
|
5596
6551
|
input: ta.Any = None, # noqa
|
@@ -5598,7 +6553,7 @@ async def asyncio_subprocess_run(
|
|
5598
6553
|
check: bool = False, # noqa
|
5599
6554
|
capture_output: ta.Optional[bool] = None,
|
5600
6555
|
**kwargs: ta.Any,
|
5601
|
-
) ->
|
6556
|
+
) -> AsyncioSubprocessOutput:
|
5602
6557
|
if capture_output:
|
5603
6558
|
kwargs.setdefault('stdout', subprocess.PIPE)
|
5604
6559
|
kwargs.setdefault('stderr', subprocess.PIPE)
|
@@ -5617,7 +6572,11 @@ async def asyncio_subprocess_run(
|
|
5617
6572
|
stderr=stderr,
|
5618
6573
|
)
|
5619
6574
|
|
5620
|
-
return
|
6575
|
+
return AsyncioSubprocessOutput(
|
6576
|
+
proc,
|
6577
|
+
stdout,
|
6578
|
+
stderr,
|
6579
|
+
)
|
5621
6580
|
|
5622
6581
|
|
5623
6582
|
##
|
@@ -5630,7 +6589,7 @@ async def asyncio_subprocess_check_call(
|
|
5630
6589
|
timeout: ta.Optional[float] = None,
|
5631
6590
|
**kwargs: ta.Any,
|
5632
6591
|
) -> None:
|
5633
|
-
|
6592
|
+
await asyncio_subprocess_run(
|
5634
6593
|
*args,
|
5635
6594
|
stdout=stdout,
|
5636
6595
|
input=input,
|
@@ -5646,7 +6605,7 @@ async def asyncio_subprocess_check_output(
|
|
5646
6605
|
timeout: ta.Optional[float] = None,
|
5647
6606
|
**kwargs: ta.Any,
|
5648
6607
|
) -> bytes:
|
5649
|
-
|
6608
|
+
out = await asyncio_subprocess_run(
|
5650
6609
|
*args,
|
5651
6610
|
stdout=asyncio.subprocess.PIPE,
|
5652
6611
|
input=input,
|
@@ -5655,7 +6614,7 @@ async def asyncio_subprocess_check_output(
|
|
5655
6614
|
**kwargs,
|
5656
6615
|
)
|
5657
6616
|
|
5658
|
-
return check.not_none(stdout)
|
6617
|
+
return check.not_none(out.stdout)
|
5659
6618
|
|
5660
6619
|
|
5661
6620
|
async def asyncio_subprocess_check_output_str(*args: str, **kwargs: ta.Any) -> str:
|
@@ -5811,6 +6770,21 @@ class InterpInspector:
|
|
5811
6770
|
INTERP_INSPECTOR = InterpInspector()
|
5812
6771
|
|
5813
6772
|
|
6773
|
+
########################################
|
6774
|
+
# ../bootstrap.py
|
6775
|
+
|
6776
|
+
|
6777
|
+
@dc.dataclass(frozen=True)
|
6778
|
+
class MainBootstrap:
|
6779
|
+
main_config: MainConfig = MainConfig()
|
6780
|
+
|
6781
|
+
deploy_config: DeployConfig = DeployConfig()
|
6782
|
+
|
6783
|
+
remote_config: RemoteConfig = RemoteConfig()
|
6784
|
+
|
6785
|
+
system_config: SystemConfig = SystemConfig()
|
6786
|
+
|
6787
|
+
|
5814
6788
|
########################################
|
5815
6789
|
# ../commands/subprocess.py
|
5816
6790
|
|
@@ -5882,146 +6856,190 @@ class SubprocessCommandExecutor(CommandExecutor[SubprocessCommand, SubprocessCom
|
|
5882
6856
|
|
5883
6857
|
|
5884
6858
|
########################################
|
5885
|
-
# ../
|
6859
|
+
# ../deploy/git.py
|
6860
|
+
"""
|
6861
|
+
TODO:
|
6862
|
+
- 'repos'?
|
6863
|
+
|
6864
|
+
git/github.com/wrmsr/omlish <- bootstrap repo
|
6865
|
+
- shallow clone off bootstrap into /apps
|
6866
|
+
|
6867
|
+
github.com/wrmsr/omlish@rev
|
6868
|
+
"""
|
5886
6869
|
|
5887
6870
|
|
5888
6871
|
##
|
5889
6872
|
|
5890
6873
|
|
5891
|
-
|
5892
|
-
|
5893
|
-
|
5894
|
-
|
6874
|
+
@dc.dataclass(frozen=True)
|
6875
|
+
class DeployGitRepo:
|
6876
|
+
host: ta.Optional[str] = None
|
6877
|
+
username: ta.Optional[str] = None
|
6878
|
+
path: ta.Optional[str] = None
|
5895
6879
|
|
5896
|
-
def
|
5897
|
-
|
5898
|
-
self.
|
6880
|
+
def __post_init__(self) -> None:
|
6881
|
+
check.not_in('..', check.non_empty_str(self.host))
|
6882
|
+
check.not_in('.', check.non_empty_str(self.path))
|
6883
|
+
|
6884
|
+
|
6885
|
+
@dc.dataclass(frozen=True)
|
6886
|
+
class DeployGitSpec:
|
6887
|
+
repo: DeployGitRepo
|
6888
|
+
rev: DeployRev
|
5899
6889
|
|
5900
6890
|
|
5901
6891
|
##
|
5902
6892
|
|
5903
6893
|
|
5904
|
-
class
|
6894
|
+
class DeployGitManager(DeployPathOwner):
|
5905
6895
|
def __init__(
|
5906
6896
|
self,
|
5907
|
-
|
6897
|
+
*,
|
6898
|
+
deploy_home: DeployHome,
|
5908
6899
|
) -> None:
|
5909
6900
|
super().__init__()
|
5910
6901
|
|
5911
|
-
self.
|
5912
|
-
|
5913
|
-
self.__bootstrap: ta.Optional[MainBootstrap] = None
|
5914
|
-
self.__injector: ta.Optional[Injector] = None
|
5915
|
-
|
5916
|
-
@property
|
5917
|
-
def _bootstrap(self) -> MainBootstrap:
|
5918
|
-
return check.not_none(self.__bootstrap)
|
5919
|
-
|
5920
|
-
@property
|
5921
|
-
def _injector(self) -> Injector:
|
5922
|
-
return check.not_none(self.__injector)
|
6902
|
+
self._deploy_home = deploy_home
|
6903
|
+
self._dir = os.path.join(deploy_home, 'git')
|
5923
6904
|
|
5924
|
-
|
6905
|
+
self._repo_dirs: ta.Dict[DeployGitRepo, DeployGitManager.RepoDir] = {}
|
5925
6906
|
|
5926
|
-
def
|
5927
|
-
|
5928
|
-
|
5929
|
-
|
5930
|
-
sig: int = signal.SIGINT,
|
5931
|
-
code: int = 1,
|
5932
|
-
) -> None:
|
5933
|
-
time.sleep(delay_s)
|
6907
|
+
def get_deploy_paths(self) -> ta.AbstractSet[DeployPath]:
|
6908
|
+
return {
|
6909
|
+
DeployPath.parse('git'),
|
6910
|
+
}
|
5934
6911
|
|
5935
|
-
|
5936
|
-
|
6912
|
+
class RepoDir:
|
6913
|
+
def __init__(
|
6914
|
+
self,
|
6915
|
+
git: 'DeployGitManager',
|
6916
|
+
repo: DeployGitRepo,
|
6917
|
+
) -> None:
|
6918
|
+
super().__init__()
|
5937
6919
|
|
5938
|
-
|
6920
|
+
self._git = git
|
6921
|
+
self._repo = repo
|
6922
|
+
self._dir = os.path.join(
|
6923
|
+
self._git._dir, # noqa
|
6924
|
+
check.non_empty_str(repo.host),
|
6925
|
+
check.non_empty_str(repo.path),
|
6926
|
+
)
|
5939
6927
|
|
5940
|
-
|
5941
|
-
|
5942
|
-
|
5943
|
-
return None
|
6928
|
+
@property
|
6929
|
+
def repo(self) -> DeployGitRepo:
|
6930
|
+
return self._repo
|
5944
6931
|
|
5945
|
-
|
5946
|
-
|
5947
|
-
|
5948
|
-
|
5949
|
-
|
6932
|
+
@property
|
6933
|
+
def url(self) -> str:
|
6934
|
+
if self._repo.username is not None:
|
6935
|
+
return f'{self._repo.username}@{self._repo.host}:{self._repo.path}'
|
6936
|
+
else:
|
6937
|
+
return f'https://{self._repo.host}/{self._repo.path}'
|
5950
6938
|
|
5951
|
-
|
6939
|
+
async def _call(self, *cmd: str) -> None:
|
6940
|
+
await asyncio_subprocess_check_call(
|
6941
|
+
*cmd,
|
6942
|
+
cwd=self._dir,
|
6943
|
+
)
|
5952
6944
|
|
5953
|
-
|
6945
|
+
@async_cached_nullary
|
6946
|
+
async def init(self) -> None:
|
6947
|
+
os.makedirs(self._dir, exist_ok=True)
|
6948
|
+
if os.path.exists(os.path.join(self._dir, '.git')):
|
6949
|
+
return
|
5954
6950
|
|
5955
|
-
|
6951
|
+
await self._call('git', 'init')
|
6952
|
+
await self._call('git', 'remote', 'add', 'origin', self.url)
|
5956
6953
|
|
5957
|
-
|
6954
|
+
async def fetch(self, rev: DeployRev) -> None:
|
6955
|
+
await self.init()
|
6956
|
+
await self._call('git', 'fetch', '--depth=1', 'origin', rev)
|
5958
6957
|
|
5959
|
-
|
5960
|
-
|
5961
|
-
return _RemoteLogHandler(self._chan)
|
6958
|
+
async def checkout(self, rev: DeployRev, dst_dir: str) -> None:
|
6959
|
+
check.state(not os.path.exists(dst_dir))
|
5962
6960
|
|
5963
|
-
|
6961
|
+
await self.fetch(rev)
|
5964
6962
|
|
5965
|
-
|
5966
|
-
|
5967
|
-
check.none(self.__injector)
|
6963
|
+
# FIXME: temp dir swap
|
6964
|
+
os.makedirs(dst_dir)
|
5968
6965
|
|
5969
|
-
|
6966
|
+
dst_call = functools.partial(asyncio_subprocess_check_call, cwd=dst_dir)
|
6967
|
+
await dst_call('git', 'init')
|
5970
6968
|
|
5971
|
-
|
6969
|
+
await dst_call('git', 'remote', 'add', 'local', self._dir)
|
6970
|
+
await dst_call('git', 'fetch', '--depth=1', 'local', rev)
|
6971
|
+
await dst_call('git', 'checkout', rev)
|
5972
6972
|
|
5973
|
-
|
5974
|
-
|
6973
|
+
def get_repo_dir(self, repo: DeployGitRepo) -> RepoDir:
|
6974
|
+
try:
|
6975
|
+
return self._repo_dirs[repo]
|
6976
|
+
except KeyError:
|
6977
|
+
repo_dir = self._repo_dirs[repo] = DeployGitManager.RepoDir(self, repo)
|
6978
|
+
return repo_dir
|
5975
6979
|
|
5976
|
-
|
6980
|
+
async def checkout(self, spec: DeployGitSpec, dst_dir: str) -> None:
|
6981
|
+
await self.get_repo_dir(spec.repo).checkout(spec.rev, dst_dir)
|
5977
6982
|
|
5978
|
-
self._chan.set_marshaler(self._injector[ObjMarshalerManager])
|
5979
6983
|
|
5980
|
-
|
6984
|
+
########################################
|
6985
|
+
# ../deploy/venvs.py
|
6986
|
+
"""
|
6987
|
+
TODO:
|
6988
|
+
- interp
|
6989
|
+
- share more code with pyproject?
|
6990
|
+
"""
|
5981
6991
|
|
5982
|
-
if self._bootstrap.remote_config.set_pgid:
|
5983
|
-
if os.getpgid(0) != os.getpid():
|
5984
|
-
log.debug('Setting pgid')
|
5985
|
-
os.setpgid(0, 0)
|
5986
6992
|
|
5987
|
-
|
5988
|
-
|
5989
|
-
|
6993
|
+
class DeployVenvManager(DeployPathOwner):
|
6994
|
+
def __init__(
|
6995
|
+
self,
|
6996
|
+
*,
|
6997
|
+
deploy_home: DeployHome,
|
6998
|
+
) -> None:
|
6999
|
+
super().__init__()
|
5990
7000
|
|
5991
|
-
self.
|
7001
|
+
self._deploy_home = deploy_home
|
7002
|
+
self._dir = os.path.join(deploy_home, 'venvs')
|
5992
7003
|
|
5993
|
-
|
5994
|
-
|
5995
|
-
|
7004
|
+
def get_deploy_paths(self) -> ta.AbstractSet[DeployPath]:
|
7005
|
+
return {
|
7006
|
+
DeployPath.parse('venvs/@app/@tag/'),
|
7007
|
+
}
|
5996
7008
|
|
5997
|
-
|
7009
|
+
async def setup_venv(
|
7010
|
+
self,
|
7011
|
+
app_dir: str,
|
7012
|
+
venv_dir: str,
|
7013
|
+
*,
|
7014
|
+
use_uv: bool = True,
|
7015
|
+
) -> None:
|
7016
|
+
sys_exe = 'python3'
|
5998
7017
|
|
5999
|
-
|
6000
|
-
await self._setup()
|
7018
|
+
await asyncio_subprocess_check_call(sys_exe, '-m', 'venv', venv_dir)
|
6001
7019
|
|
6002
|
-
|
7020
|
+
#
|
6003
7021
|
|
6004
|
-
|
7022
|
+
venv_exe = os.path.join(venv_dir, 'bin', 'python3')
|
6005
7023
|
|
6006
|
-
|
7024
|
+
#
|
6007
7025
|
|
7026
|
+
reqs_txt = os.path.join(app_dir, 'requirements.txt')
|
6008
7027
|
|
6009
|
-
|
6010
|
-
|
7028
|
+
if os.path.isfile(reqs_txt):
|
7029
|
+
if use_uv:
|
7030
|
+
await asyncio_subprocess_check_call(venv_exe, '-m', 'pip', 'install', 'uv')
|
7031
|
+
pip_cmd = ['-m', 'uv', 'pip']
|
7032
|
+
else:
|
7033
|
+
pip_cmd = ['-m', 'pip']
|
6011
7034
|
|
6012
|
-
|
6013
|
-
input = await asyncio_open_stream_reader(rt.input) # noqa
|
6014
|
-
output = await asyncio_open_stream_writer(rt.output)
|
7035
|
+
await asyncio_subprocess_check_call(venv_exe, *pip_cmd,'install', '-r', reqs_txt)
|
6015
7036
|
|
6016
|
-
|
6017
|
-
|
6018
|
-
|
7037
|
+
async def setup_app_venv(self, app_tag: DeployAppTag) -> None:
|
7038
|
+
await self.setup_venv(
|
7039
|
+
os.path.join(self._deploy_home, 'apps', app_tag.app, app_tag.tag),
|
7040
|
+
os.path.join(self._deploy_home, 'venvs', app_tag.app, app_tag.tag),
|
6019
7041
|
)
|
6020
7042
|
|
6021
|
-
await _RemoteExecutionMain(chan).run()
|
6022
|
-
|
6023
|
-
asyncio.run(inner())
|
6024
|
-
|
6025
7043
|
|
6026
7044
|
########################################
|
6027
7045
|
# ../remote/spawning.py
|
@@ -6196,25 +7214,24 @@ class AptSystemPackageManager(SystemPackageManager):
|
|
6196
7214
|
}
|
6197
7215
|
|
6198
7216
|
async def update(self) -> None:
|
6199
|
-
await asyncio_subprocess_check_call('apt', 'update', env={**os.environ, **self._APT_ENV})
|
7217
|
+
await asyncio_subprocess_check_call('sudo', 'apt', 'update', env={**os.environ, **self._APT_ENV})
|
6200
7218
|
|
6201
7219
|
async def upgrade(self) -> None:
|
6202
|
-
await asyncio_subprocess_check_call('apt', 'upgrade', '-y', env={**os.environ, **self._APT_ENV})
|
7220
|
+
await asyncio_subprocess_check_call('sudo', 'apt', 'upgrade', '-y', env={**os.environ, **self._APT_ENV})
|
6203
7221
|
|
6204
7222
|
async def install(self, *packages: SystemPackageOrStr) -> None:
|
6205
7223
|
pns = [p.name if isinstance(p, SystemPackage) else p for p in packages] # FIXME: versions
|
6206
|
-
await asyncio_subprocess_check_call('apt', 'install', '-y', *pns, env={**os.environ, **self._APT_ENV})
|
7224
|
+
await asyncio_subprocess_check_call('sudo', 'apt', 'install', '-y', *pns, env={**os.environ, **self._APT_ENV})
|
6207
7225
|
|
6208
7226
|
async def query(self, *packages: SystemPackageOrStr) -> ta.Mapping[str, SystemPackage]:
|
6209
7227
|
pns = [p.name if isinstance(p, SystemPackage) else p for p in packages]
|
6210
|
-
|
6211
|
-
|
6212
|
-
*cmd,
|
7228
|
+
out = await asyncio_subprocess_run(
|
7229
|
+
'dpkg-query', '-W', '-f=${Package}=${Version}\n', *pns,
|
6213
7230
|
capture_output=True,
|
6214
7231
|
check=False,
|
6215
7232
|
)
|
6216
7233
|
d: ta.Dict[str, SystemPackage] = {}
|
6217
|
-
for l in check.not_none(stdout).decode('utf-8').strip().splitlines():
|
7234
|
+
for l in check.not_none(out.stdout).decode('utf-8').strip().splitlines():
|
6218
7235
|
n, v = l.split('=', 1)
|
6219
7236
|
d[n] = SystemPackage(
|
6220
7237
|
name=n,
|
@@ -6223,6 +7240,33 @@ class AptSystemPackageManager(SystemPackageManager):
|
|
6223
7240
|
return d
|
6224
7241
|
|
6225
7242
|
|
7243
|
+
class YumSystemPackageManager(SystemPackageManager):
|
7244
|
+
async def update(self) -> None:
|
7245
|
+
await asyncio_subprocess_check_call('sudo', 'yum', 'check-update')
|
7246
|
+
|
7247
|
+
async def upgrade(self) -> None:
|
7248
|
+
await asyncio_subprocess_check_call('sudo', 'yum', 'update')
|
7249
|
+
|
7250
|
+
async def install(self, *packages: SystemPackageOrStr) -> None:
|
7251
|
+
pns = [p.name if isinstance(p, SystemPackage) else p for p in packages] # FIXME: versions
|
7252
|
+
await asyncio_subprocess_check_call('sudo', 'yum', 'install', *pns)
|
7253
|
+
|
7254
|
+
async def query(self, *packages: SystemPackageOrStr) -> ta.Mapping[str, SystemPackage]:
|
7255
|
+
pns = [p.name if isinstance(p, SystemPackage) else p for p in packages]
|
7256
|
+
d: ta.Dict[str, SystemPackage] = {}
|
7257
|
+
for pn in pns:
|
7258
|
+
out = await asyncio_subprocess_run(
|
7259
|
+
'rpm', '-q', pn,
|
7260
|
+
capture_output=True,
|
7261
|
+
)
|
7262
|
+
if not out.proc.returncode:
|
7263
|
+
d[pn] = SystemPackage(
|
7264
|
+
pn,
|
7265
|
+
check.not_none(out.stdout).decode().strip(),
|
7266
|
+
)
|
7267
|
+
return d
|
7268
|
+
|
7269
|
+
|
6226
7270
|
########################################
|
6227
7271
|
# ../../../omdev/interp/providers.py
|
6228
7272
|
"""
|
@@ -6285,139 +7329,342 @@ class RunningInterpProvider(InterpProvider):
|
|
6285
7329
|
|
6286
7330
|
|
6287
7331
|
########################################
|
6288
|
-
# ../
|
7332
|
+
# ../commands/inject.py
|
6289
7333
|
|
6290
7334
|
|
6291
7335
|
##
|
6292
7336
|
|
6293
7337
|
|
6294
|
-
|
6295
|
-
|
6296
|
-
|
6297
|
-
|
6298
|
-
|
6299
|
-
|
6300
|
-
|
6301
|
-
|
7338
|
+
def bind_command(
|
7339
|
+
command_cls: ta.Type[Command],
|
7340
|
+
executor_cls: ta.Optional[ta.Type[CommandExecutor]],
|
7341
|
+
) -> InjectorBindings:
|
7342
|
+
lst: ta.List[InjectorBindingOrBindings] = [
|
7343
|
+
inj.bind(CommandRegistration(command_cls), array=True),
|
7344
|
+
]
|
7345
|
+
|
7346
|
+
if executor_cls is not None:
|
7347
|
+
lst.extend([
|
7348
|
+
inj.bind(executor_cls, singleton=True),
|
7349
|
+
inj.bind(CommandExecutorRegistration(command_cls, executor_cls), array=True),
|
7350
|
+
])
|
7351
|
+
|
7352
|
+
return inj.as_bindings(*lst)
|
6302
7353
|
|
6303
7354
|
|
6304
7355
|
##
|
6305
7356
|
|
6306
7357
|
|
6307
|
-
|
6308
|
-
|
6309
|
-
|
6310
|
-
*,
|
6311
|
-
spawning: RemoteSpawning,
|
6312
|
-
msh: ObjMarshalerManager,
|
6313
|
-
payload_file: ta.Optional[RemoteExecutionPayloadFile] = None,
|
6314
|
-
) -> None:
|
6315
|
-
super().__init__()
|
7358
|
+
@dc.dataclass(frozen=True)
|
7359
|
+
class _FactoryCommandExecutor(CommandExecutor):
|
7360
|
+
factory: ta.Callable[[], CommandExecutor]
|
6316
7361
|
|
6317
|
-
|
6318
|
-
self.
|
6319
|
-
|
7362
|
+
def execute(self, i: Command) -> ta.Awaitable[Command.Output]:
|
7363
|
+
return self.factory().execute(i)
|
7364
|
+
|
7365
|
+
|
7366
|
+
##
|
7367
|
+
|
7368
|
+
|
7369
|
+
def bind_commands(
|
7370
|
+
*,
|
7371
|
+
main_config: MainConfig,
|
7372
|
+
) -> InjectorBindings:
|
7373
|
+
lst: ta.List[InjectorBindingOrBindings] = [
|
7374
|
+
inj.bind_array(CommandRegistration),
|
7375
|
+
inj.bind_array_type(CommandRegistration, CommandRegistrations),
|
7376
|
+
|
7377
|
+
inj.bind_array(CommandExecutorRegistration),
|
7378
|
+
inj.bind_array_type(CommandExecutorRegistration, CommandExecutorRegistrations),
|
7379
|
+
|
7380
|
+
inj.bind(build_command_name_map, singleton=True),
|
7381
|
+
]
|
6320
7382
|
|
6321
7383
|
#
|
6322
7384
|
|
6323
|
-
|
6324
|
-
|
6325
|
-
return get_remote_payload_src(file=self._payload_file)
|
7385
|
+
def provide_obj_marshaler_installer(cmds: CommandNameMap) -> ObjMarshalerInstaller:
|
7386
|
+
return ObjMarshalerInstaller(functools.partial(install_command_marshaling, cmds))
|
6326
7387
|
|
6327
|
-
|
6328
|
-
def _remote_src(self) -> ta.Sequence[str]:
|
6329
|
-
return [
|
6330
|
-
self._payload_src(),
|
6331
|
-
'_remote_execution_main()',
|
6332
|
-
]
|
7388
|
+
lst.append(inj.bind(provide_obj_marshaler_installer, array=True))
|
6333
7389
|
|
6334
|
-
|
6335
|
-
|
6336
|
-
|
7390
|
+
#
|
7391
|
+
|
7392
|
+
def provide_command_executor_map(
|
7393
|
+
injector: Injector,
|
7394
|
+
crs: CommandExecutorRegistrations,
|
7395
|
+
) -> CommandExecutorMap:
|
7396
|
+
dct: ta.Dict[ta.Type[Command], CommandExecutor] = {}
|
7397
|
+
|
7398
|
+
cr: CommandExecutorRegistration
|
7399
|
+
for cr in crs:
|
7400
|
+
if cr.command_cls in dct:
|
7401
|
+
raise KeyError(cr.command_cls)
|
7402
|
+
|
7403
|
+
factory = functools.partial(injector.provide, cr.executor_cls)
|
7404
|
+
if main_config.debug:
|
7405
|
+
ce = factory()
|
7406
|
+
else:
|
7407
|
+
ce = _FactoryCommandExecutor(factory)
|
7408
|
+
|
7409
|
+
dct[cr.command_cls] = ce
|
7410
|
+
|
7411
|
+
return CommandExecutorMap(dct)
|
7412
|
+
|
7413
|
+
lst.extend([
|
7414
|
+
inj.bind(provide_command_executor_map, singleton=True),
|
7415
|
+
|
7416
|
+
inj.bind(LocalCommandExecutor, singleton=True, eager=main_config.debug),
|
7417
|
+
])
|
6337
7418
|
|
6338
7419
|
#
|
6339
7420
|
|
6340
|
-
|
6341
|
-
|
7421
|
+
lst.extend([
|
7422
|
+
bind_command(PingCommand, PingCommandExecutor),
|
7423
|
+
bind_command(SubprocessCommand, SubprocessCommandExecutor),
|
7424
|
+
])
|
7425
|
+
|
7426
|
+
#
|
7427
|
+
|
7428
|
+
return inj.as_bindings(*lst)
|
7429
|
+
|
7430
|
+
|
7431
|
+
########################################
|
7432
|
+
# ../deploy/apps.py
|
7433
|
+
|
7434
|
+
|
7435
|
+
def make_deploy_tag(
|
7436
|
+
rev: DeployRev,
|
7437
|
+
now: ta.Optional[datetime.datetime] = None,
|
7438
|
+
) -> DeployTag:
|
7439
|
+
if now is None:
|
7440
|
+
now = datetime.datetime.utcnow() # noqa
|
7441
|
+
now_fmt = '%Y%m%dT%H%M%S'
|
7442
|
+
now_str = now.strftime(now_fmt)
|
7443
|
+
return DeployTag('-'.join([rev, now_str]))
|
7444
|
+
|
7445
|
+
|
7446
|
+
class DeployAppManager(DeployPathOwner):
|
7447
|
+
def __init__(
|
6342
7448
|
self,
|
6343
|
-
|
6344
|
-
|
6345
|
-
|
6346
|
-
|
6347
|
-
|
7449
|
+
*,
|
7450
|
+
deploy_home: DeployHome,
|
7451
|
+
git: DeployGitManager,
|
7452
|
+
venvs: DeployVenvManager,
|
7453
|
+
) -> None:
|
7454
|
+
super().__init__()
|
6348
7455
|
|
6349
|
-
|
6350
|
-
|
6351
|
-
|
6352
|
-
debug=bs.main_config.debug,
|
6353
|
-
) as proc:
|
6354
|
-
res = await PyremoteBootstrapDriver( # noqa
|
6355
|
-
remote_src,
|
6356
|
-
PyremoteBootstrapOptions(
|
6357
|
-
debug=bs.main_config.debug,
|
6358
|
-
),
|
6359
|
-
).async_run(
|
6360
|
-
proc.stdout,
|
6361
|
-
proc.stdin,
|
6362
|
-
)
|
7456
|
+
self._deploy_home = deploy_home
|
7457
|
+
self._git = git
|
7458
|
+
self._venvs = venvs
|
6363
7459
|
|
6364
|
-
|
6365
|
-
proc.stdout,
|
6366
|
-
proc.stdin,
|
6367
|
-
msh=self._msh,
|
6368
|
-
)
|
7460
|
+
self._dir = os.path.join(deploy_home, 'apps')
|
6369
7461
|
|
6370
|
-
|
7462
|
+
def get_deploy_paths(self) -> ta.AbstractSet[DeployPath]:
|
7463
|
+
return {
|
7464
|
+
DeployPath.parse('apps/@app/@tag'),
|
7465
|
+
}
|
6371
7466
|
|
6372
|
-
|
6373
|
-
|
6374
|
-
|
7467
|
+
async def prepare_app(
|
7468
|
+
self,
|
7469
|
+
app: DeployApp,
|
7470
|
+
rev: DeployRev,
|
7471
|
+
repo: DeployGitRepo,
|
7472
|
+
):
|
7473
|
+
app_tag = DeployAppTag(app, make_deploy_tag(rev))
|
7474
|
+
app_dir = os.path.join(self._dir, app, app_tag.tag)
|
7475
|
+
|
7476
|
+
#
|
7477
|
+
|
7478
|
+
await self._git.checkout(
|
7479
|
+
DeployGitSpec(
|
7480
|
+
repo=repo,
|
7481
|
+
rev=rev,
|
7482
|
+
),
|
7483
|
+
app_dir,
|
7484
|
+
)
|
7485
|
+
|
7486
|
+
#
|
7487
|
+
|
7488
|
+
await self._venvs.setup_app_venv(app_tag)
|
6375
7489
|
|
6376
|
-
|
7490
|
+
|
7491
|
+
########################################
|
7492
|
+
# ../remote/_main.py
|
7493
|
+
|
7494
|
+
|
7495
|
+
##
|
7496
|
+
|
7497
|
+
|
7498
|
+
class _RemoteExecutionLogHandler(logging.Handler):
|
7499
|
+
def __init__(self, fn: ta.Callable[[str], None]) -> None:
|
7500
|
+
super().__init__()
|
7501
|
+
self._fn = fn
|
7502
|
+
|
7503
|
+
def emit(self, record):
|
7504
|
+
msg = self.format(record)
|
7505
|
+
self._fn(msg)
|
6377
7506
|
|
6378
7507
|
|
6379
7508
|
##
|
6380
7509
|
|
6381
7510
|
|
6382
|
-
class
|
7511
|
+
class _RemoteExecutionMain:
|
6383
7512
|
def __init__(
|
6384
7513
|
self,
|
6385
|
-
|
6386
|
-
msh: ObjMarshalerManager,
|
6387
|
-
local_executor: LocalCommandExecutor,
|
7514
|
+
chan: RemoteChannel,
|
6388
7515
|
) -> None:
|
6389
7516
|
super().__init__()
|
6390
7517
|
|
6391
|
-
self.
|
6392
|
-
self._local_executor = local_executor
|
7518
|
+
self._chan = chan
|
6393
7519
|
|
6394
|
-
|
6395
|
-
|
7520
|
+
self.__bootstrap: ta.Optional[MainBootstrap] = None
|
7521
|
+
self.__injector: ta.Optional[Injector] = None
|
7522
|
+
|
7523
|
+
@property
|
7524
|
+
def _bootstrap(self) -> MainBootstrap:
|
7525
|
+
return check.not_none(self.__bootstrap)
|
7526
|
+
|
7527
|
+
@property
|
7528
|
+
def _injector(self) -> Injector:
|
7529
|
+
return check.not_none(self.__injector)
|
7530
|
+
|
7531
|
+
#
|
7532
|
+
|
7533
|
+
def _timebomb_main(
|
6396
7534
|
self,
|
6397
|
-
|
6398
|
-
|
6399
|
-
|
6400
|
-
|
6401
|
-
|
7535
|
+
delay_s: float,
|
7536
|
+
*,
|
7537
|
+
sig: int = signal.SIGINT,
|
7538
|
+
code: int = 1,
|
7539
|
+
) -> None:
|
7540
|
+
time.sleep(delay_s)
|
6402
7541
|
|
6403
|
-
|
6404
|
-
|
7542
|
+
if (pgid := os.getpgid(0)) == os.getpid():
|
7543
|
+
os.killpg(pgid, sig)
|
6405
7544
|
|
6406
|
-
|
6407
|
-
|
6408
|
-
|
7545
|
+
os._exit(code) # noqa
|
7546
|
+
|
7547
|
+
@cached_nullary
|
7548
|
+
def _timebomb_thread(self) -> ta.Optional[threading.Thread]:
|
7549
|
+
if (tbd := self._bootstrap.remote_config.timebomb_delay_s) is None:
|
7550
|
+
return None
|
7551
|
+
|
7552
|
+
thr = threading.Thread(
|
7553
|
+
target=functools.partial(self._timebomb_main, tbd),
|
7554
|
+
name=f'{self.__class__.__name__}.timebomb',
|
7555
|
+
daemon=True,
|
6409
7556
|
)
|
6410
|
-
rch_task = asyncio.create_task(rch.run()) # noqa
|
6411
|
-
try:
|
6412
|
-
rce: RemoteCommandExecutor
|
6413
|
-
async with contextlib.aclosing(RemoteCommandExecutor(local_chan)) as rce:
|
6414
|
-
await rce.start()
|
6415
7557
|
|
6416
|
-
|
7558
|
+
thr.start()
|
6417
7559
|
|
6418
|
-
|
6419
|
-
|
6420
|
-
|
7560
|
+
log.debug('Started timebomb thread: %r', thr)
|
7561
|
+
|
7562
|
+
return thr
|
7563
|
+
|
7564
|
+
#
|
7565
|
+
|
7566
|
+
@cached_nullary
|
7567
|
+
def _log_handler(self) -> _RemoteLogHandler:
|
7568
|
+
return _RemoteLogHandler(self._chan)
|
7569
|
+
|
7570
|
+
#
|
7571
|
+
|
7572
|
+
async def _setup(self) -> None:
|
7573
|
+
check.none(self.__bootstrap)
|
7574
|
+
check.none(self.__injector)
|
7575
|
+
|
7576
|
+
# Bootstrap
|
7577
|
+
|
7578
|
+
self.__bootstrap = check.not_none(await self._chan.recv_obj(MainBootstrap))
|
7579
|
+
|
7580
|
+
if (prd := self._bootstrap.remote_config.pycharm_remote_debug) is not None:
|
7581
|
+
pycharm_debug_connect(prd)
|
7582
|
+
|
7583
|
+
self.__injector = main_bootstrap(self._bootstrap)
|
7584
|
+
|
7585
|
+
self._chan.set_marshaler(self._injector[ObjMarshalerManager])
|
7586
|
+
|
7587
|
+
# Post-bootstrap
|
7588
|
+
|
7589
|
+
if self._bootstrap.remote_config.set_pgid:
|
7590
|
+
if os.getpgid(0) != os.getpid():
|
7591
|
+
log.debug('Setting pgid')
|
7592
|
+
os.setpgid(0, 0)
|
7593
|
+
|
7594
|
+
if (ds := self._bootstrap.remote_config.deathsig) is not None:
|
7595
|
+
log.debug('Setting deathsig: %s', ds)
|
7596
|
+
set_process_deathsig(int(signal.Signals[f'SIG{ds.upper()}']))
|
7597
|
+
|
7598
|
+
self._timebomb_thread()
|
7599
|
+
|
7600
|
+
if self._bootstrap.remote_config.forward_logging:
|
7601
|
+
log.debug('Installing log forwarder')
|
7602
|
+
logging.root.addHandler(self._log_handler())
|
7603
|
+
|
7604
|
+
#
|
7605
|
+
|
7606
|
+
async def run(self) -> None:
|
7607
|
+
await self._setup()
|
7608
|
+
|
7609
|
+
executor = self._injector[LocalCommandExecutor]
|
7610
|
+
|
7611
|
+
handler = _RemoteCommandHandler(self._chan, executor)
|
7612
|
+
|
7613
|
+
await handler.run()
|
7614
|
+
|
7615
|
+
|
7616
|
+
def _remote_execution_main() -> None:
|
7617
|
+
rt = pyremote_bootstrap_finalize() # noqa
|
7618
|
+
|
7619
|
+
async def inner() -> None:
|
7620
|
+
input = await asyncio_open_stream_reader(rt.input) # noqa
|
7621
|
+
output = await asyncio_open_stream_writer(rt.output)
|
7622
|
+
|
7623
|
+
chan = RemoteChannelImpl(
|
7624
|
+
input,
|
7625
|
+
output,
|
7626
|
+
)
|
7627
|
+
|
7628
|
+
await _RemoteExecutionMain(chan).run()
|
7629
|
+
|
7630
|
+
asyncio.run(inner())
|
7631
|
+
|
7632
|
+
|
7633
|
+
########################################
|
7634
|
+
# ../system/commands.py
|
7635
|
+
|
7636
|
+
|
7637
|
+
##
|
7638
|
+
|
7639
|
+
|
7640
|
+
@dc.dataclass(frozen=True)
|
7641
|
+
class CheckSystemPackageCommand(Command['CheckSystemPackageCommand.Output']):
|
7642
|
+
pkgs: ta.Sequence[str] = ()
|
7643
|
+
|
7644
|
+
def __post_init__(self) -> None:
|
7645
|
+
check.not_isinstance(self.pkgs, str)
|
7646
|
+
|
7647
|
+
@dc.dataclass(frozen=True)
|
7648
|
+
class Output(Command.Output):
|
7649
|
+
pkgs: ta.Sequence[SystemPackage]
|
7650
|
+
|
7651
|
+
|
7652
|
+
class CheckSystemPackageCommandExecutor(CommandExecutor[CheckSystemPackageCommand, CheckSystemPackageCommand.Output]):
|
7653
|
+
def __init__(
|
7654
|
+
self,
|
7655
|
+
*,
|
7656
|
+
mgr: SystemPackageManager,
|
7657
|
+
) -> None:
|
7658
|
+
super().__init__()
|
7659
|
+
|
7660
|
+
self._mgr = mgr
|
7661
|
+
|
7662
|
+
async def execute(self, cmd: CheckSystemPackageCommand) -> CheckSystemPackageCommand.Output:
|
7663
|
+
log.info('Checking system package!')
|
7664
|
+
|
7665
|
+
ret = await self._mgr.query(*cmd.pkgs)
|
7666
|
+
|
7667
|
+
return CheckSystemPackageCommand.Output(list(ret.values()))
|
6421
7668
|
|
6422
7669
|
|
6423
7670
|
########################################
|
@@ -6961,54 +8208,183 @@ class SystemInterpProvider(InterpProvider):
|
|
6961
8208
|
lst.append((e, ev))
|
6962
8209
|
return lst
|
6963
8210
|
|
6964
|
-
#
|
8211
|
+
#
|
8212
|
+
|
8213
|
+
async def get_installed_versions(self, spec: InterpSpecifier) -> ta.Sequence[InterpVersion]:
|
8214
|
+
return [ev for e, ev in await self.exe_versions()]
|
8215
|
+
|
8216
|
+
async def get_installed_version(self, version: InterpVersion) -> Interp:
|
8217
|
+
for e, ev in await self.exe_versions():
|
8218
|
+
if ev != version:
|
8219
|
+
continue
|
8220
|
+
return Interp(
|
8221
|
+
exe=e,
|
8222
|
+
version=ev,
|
8223
|
+
)
|
8224
|
+
raise KeyError(version)
|
8225
|
+
|
8226
|
+
|
8227
|
+
########################################
|
8228
|
+
# ../remote/connection.py
|
8229
|
+
|
8230
|
+
|
8231
|
+
##
|
8232
|
+
|
8233
|
+
|
8234
|
+
class PyremoteRemoteExecutionConnector:
|
8235
|
+
def __init__(
|
8236
|
+
self,
|
8237
|
+
*,
|
8238
|
+
spawning: RemoteSpawning,
|
8239
|
+
msh: ObjMarshalerManager,
|
8240
|
+
payload_file: ta.Optional[RemoteExecutionPayloadFile] = None,
|
8241
|
+
) -> None:
|
8242
|
+
super().__init__()
|
8243
|
+
|
8244
|
+
self._spawning = spawning
|
8245
|
+
self._msh = msh
|
8246
|
+
self._payload_file = payload_file
|
8247
|
+
|
8248
|
+
#
|
8249
|
+
|
8250
|
+
@cached_nullary
|
8251
|
+
def _payload_src(self) -> str:
|
8252
|
+
return get_remote_payload_src(file=self._payload_file)
|
8253
|
+
|
8254
|
+
@cached_nullary
|
8255
|
+
def _remote_src(self) -> ta.Sequence[str]:
|
8256
|
+
return [
|
8257
|
+
self._payload_src(),
|
8258
|
+
'_remote_execution_main()',
|
8259
|
+
]
|
8260
|
+
|
8261
|
+
@cached_nullary
|
8262
|
+
def _spawn_src(self) -> str:
|
8263
|
+
return pyremote_build_bootstrap_cmd(__package__ or 'manage')
|
8264
|
+
|
8265
|
+
#
|
8266
|
+
|
8267
|
+
@contextlib.asynccontextmanager
|
8268
|
+
async def connect(
|
8269
|
+
self,
|
8270
|
+
tgt: RemoteSpawning.Target,
|
8271
|
+
bs: MainBootstrap,
|
8272
|
+
) -> ta.AsyncGenerator[RemoteCommandExecutor, None]:
|
8273
|
+
spawn_src = self._spawn_src()
|
8274
|
+
remote_src = self._remote_src()
|
8275
|
+
|
8276
|
+
async with self._spawning.spawn(
|
8277
|
+
tgt,
|
8278
|
+
spawn_src,
|
8279
|
+
debug=bs.main_config.debug,
|
8280
|
+
) as proc:
|
8281
|
+
res = await PyremoteBootstrapDriver( # noqa
|
8282
|
+
remote_src,
|
8283
|
+
PyremoteBootstrapOptions(
|
8284
|
+
debug=bs.main_config.debug,
|
8285
|
+
),
|
8286
|
+
).async_run(
|
8287
|
+
proc.stdout,
|
8288
|
+
proc.stdin,
|
8289
|
+
)
|
8290
|
+
|
8291
|
+
chan = RemoteChannelImpl(
|
8292
|
+
proc.stdout,
|
8293
|
+
proc.stdin,
|
8294
|
+
msh=self._msh,
|
8295
|
+
)
|
8296
|
+
|
8297
|
+
await chan.send_obj(bs)
|
8298
|
+
|
8299
|
+
rce: RemoteCommandExecutor
|
8300
|
+
async with aclosing(RemoteCommandExecutor(chan)) as rce:
|
8301
|
+
await rce.start()
|
8302
|
+
|
8303
|
+
yield rce
|
8304
|
+
|
8305
|
+
|
8306
|
+
##
|
8307
|
+
|
8308
|
+
|
8309
|
+
class InProcessRemoteExecutionConnector:
|
8310
|
+
def __init__(
|
8311
|
+
self,
|
8312
|
+
*,
|
8313
|
+
msh: ObjMarshalerManager,
|
8314
|
+
local_executor: LocalCommandExecutor,
|
8315
|
+
) -> None:
|
8316
|
+
super().__init__()
|
8317
|
+
|
8318
|
+
self._msh = msh
|
8319
|
+
self._local_executor = local_executor
|
8320
|
+
|
8321
|
+
@contextlib.asynccontextmanager
|
8322
|
+
async def connect(self) -> ta.AsyncGenerator[RemoteCommandExecutor, None]:
|
8323
|
+
r0, w0 = asyncio_create_bytes_channel()
|
8324
|
+
r1, w1 = asyncio_create_bytes_channel()
|
8325
|
+
|
8326
|
+
remote_chan = RemoteChannelImpl(r0, w1, msh=self._msh)
|
8327
|
+
local_chan = RemoteChannelImpl(r1, w0, msh=self._msh)
|
6965
8328
|
|
6966
|
-
|
6967
|
-
|
8329
|
+
rch = _RemoteCommandHandler(
|
8330
|
+
remote_chan,
|
8331
|
+
self._local_executor,
|
8332
|
+
)
|
8333
|
+
rch_task = asyncio.create_task(rch.run()) # noqa
|
8334
|
+
try:
|
8335
|
+
rce: RemoteCommandExecutor
|
8336
|
+
async with aclosing(RemoteCommandExecutor(local_chan)) as rce:
|
8337
|
+
await rce.start()
|
6968
8338
|
|
6969
|
-
|
6970
|
-
|
6971
|
-
|
6972
|
-
|
6973
|
-
|
6974
|
-
exe=e,
|
6975
|
-
version=ev,
|
6976
|
-
)
|
6977
|
-
raise KeyError(version)
|
8339
|
+
yield rce
|
8340
|
+
|
8341
|
+
finally:
|
8342
|
+
rch.stop()
|
8343
|
+
await rch_task
|
6978
8344
|
|
6979
8345
|
|
6980
8346
|
########################################
|
6981
|
-
# ../
|
8347
|
+
# ../system/inject.py
|
6982
8348
|
|
6983
8349
|
|
6984
|
-
def
|
8350
|
+
def bind_system(
|
6985
8351
|
*,
|
6986
|
-
|
8352
|
+
system_config: SystemConfig,
|
6987
8353
|
) -> InjectorBindings:
|
6988
8354
|
lst: ta.List[InjectorBindingOrBindings] = [
|
6989
|
-
inj.bind(
|
6990
|
-
|
6991
|
-
inj.bind(SubprocessRemoteSpawning, singleton=True),
|
6992
|
-
inj.bind(RemoteSpawning, to_key=SubprocessRemoteSpawning),
|
8355
|
+
inj.bind(system_config),
|
6993
8356
|
]
|
6994
8357
|
|
6995
8358
|
#
|
6996
8359
|
|
6997
|
-
|
8360
|
+
platform = system_config.platform or detect_system_platform()
|
8361
|
+
lst.append(inj.bind(platform, key=Platform))
|
8362
|
+
|
8363
|
+
#
|
8364
|
+
|
8365
|
+
if isinstance(platform, AmazonLinuxPlatform):
|
6998
8366
|
lst.extend([
|
6999
|
-
inj.bind(
|
7000
|
-
inj.bind(
|
8367
|
+
inj.bind(YumSystemPackageManager, singleton=True),
|
8368
|
+
inj.bind(SystemPackageManager, to_key=YumSystemPackageManager),
|
7001
8369
|
])
|
7002
|
-
|
8370
|
+
|
8371
|
+
elif isinstance(platform, LinuxPlatform):
|
8372
|
+
lst.extend([
|
8373
|
+
inj.bind(AptSystemPackageManager, singleton=True),
|
8374
|
+
inj.bind(SystemPackageManager, to_key=AptSystemPackageManager),
|
8375
|
+
])
|
8376
|
+
|
8377
|
+
elif isinstance(platform, DarwinPlatform):
|
7003
8378
|
lst.extend([
|
7004
|
-
inj.bind(
|
7005
|
-
inj.bind(
|
8379
|
+
inj.bind(BrewSystemPackageManager, singleton=True),
|
8380
|
+
inj.bind(SystemPackageManager, to_key=BrewSystemPackageManager),
|
7006
8381
|
])
|
7007
8382
|
|
7008
8383
|
#
|
7009
8384
|
|
7010
|
-
|
7011
|
-
|
8385
|
+
lst.extend([
|
8386
|
+
bind_command(CheckSystemPackageCommand, CheckSystemPackageCommandExecutor),
|
8387
|
+
])
|
7012
8388
|
|
7013
8389
|
#
|
7014
8390
|
|
@@ -7114,189 +8490,248 @@ DEFAULT_INTERP_RESOLVER = InterpResolver([(p.name, p) for p in [
|
|
7114
8490
|
|
7115
8491
|
|
7116
8492
|
########################################
|
7117
|
-
# ../
|
8493
|
+
# ../remote/inject.py
|
7118
8494
|
|
7119
8495
|
|
7120
|
-
|
8496
|
+
def bind_remote(
|
8497
|
+
*,
|
8498
|
+
remote_config: RemoteConfig,
|
8499
|
+
) -> InjectorBindings:
|
8500
|
+
lst: ta.List[InjectorBindingOrBindings] = [
|
8501
|
+
inj.bind(remote_config),
|
7121
8502
|
|
8503
|
+
inj.bind(SubprocessRemoteSpawning, singleton=True),
|
8504
|
+
inj.bind(RemoteSpawning, to_key=SubprocessRemoteSpawning),
|
7122
8505
|
|
7123
|
-
|
7124
|
-
|
7125
|
-
|
7126
|
-
install: bool = False
|
8506
|
+
inj.bind(PyremoteRemoteExecutionConnector, singleton=True),
|
8507
|
+
inj.bind(InProcessRemoteExecutionConnector, singleton=True),
|
8508
|
+
]
|
7127
8509
|
|
7128
|
-
|
7129
|
-
|
7130
|
-
|
7131
|
-
|
7132
|
-
opts: InterpOpts
|
8510
|
+
#
|
8511
|
+
|
8512
|
+
if (pf := remote_config.payload_file) is not None:
|
8513
|
+
lst.append(inj.bind(pf, key=RemoteExecutionPayloadFile))
|
7133
8514
|
|
8515
|
+
#
|
7134
8516
|
|
7135
|
-
|
7136
|
-
async def execute(self, cmd: InterpCommand) -> InterpCommand.Output:
|
7137
|
-
i = InterpSpecifier.parse(check.not_none(cmd.spec))
|
7138
|
-
o = check.not_none(await DEFAULT_INTERP_RESOLVER.resolve(i, install=cmd.install))
|
7139
|
-
return InterpCommand.Output(
|
7140
|
-
exe=o.exe,
|
7141
|
-
version=str(o.version.version),
|
7142
|
-
opts=o.version.opts,
|
7143
|
-
)
|
8517
|
+
return inj.as_bindings(*lst)
|
7144
8518
|
|
7145
8519
|
|
7146
8520
|
########################################
|
7147
|
-
# ../
|
8521
|
+
# ../targets/connection.py
|
7148
8522
|
|
7149
8523
|
|
7150
8524
|
##
|
7151
8525
|
|
7152
8526
|
|
7153
|
-
|
7154
|
-
|
7155
|
-
|
7156
|
-
|
7157
|
-
lst: ta.List[InjectorBindingOrBindings] = [
|
7158
|
-
inj.bind(CommandRegistration(command_cls), array=True),
|
7159
|
-
]
|
8527
|
+
class ManageTargetConnector(abc.ABC):
|
8528
|
+
@abc.abstractmethod
|
8529
|
+
def connect(self, tgt: ManageTarget) -> ta.AsyncContextManager[CommandExecutor]:
|
8530
|
+
raise NotImplementedError
|
7160
8531
|
|
7161
|
-
if executor_cls is not None:
|
7162
|
-
lst.extend([
|
7163
|
-
inj.bind(executor_cls, singleton=True),
|
7164
|
-
inj.bind(CommandExecutorRegistration(command_cls, executor_cls), array=True),
|
7165
|
-
])
|
7166
8532
|
|
7167
|
-
|
8533
|
+
##
|
7168
8534
|
|
7169
8535
|
|
7170
|
-
|
8536
|
+
ManageTargetConnectorMap = ta.NewType('ManageTargetConnectorMap', ta.Mapping[ta.Type[ManageTarget], ManageTargetConnector]) # noqa
|
7171
8537
|
|
7172
8538
|
|
7173
8539
|
@dc.dataclass(frozen=True)
|
7174
|
-
class
|
7175
|
-
|
8540
|
+
class TypeSwitchedManageTargetConnector(ManageTargetConnector):
|
8541
|
+
connectors: ManageTargetConnectorMap
|
7176
8542
|
|
7177
|
-
def
|
7178
|
-
|
8543
|
+
def get_connector(self, ty: ta.Type[ManageTarget]) -> ManageTargetConnector:
|
8544
|
+
for k, v in self.connectors.items():
|
8545
|
+
if issubclass(ty, k):
|
8546
|
+
return v
|
8547
|
+
raise KeyError(ty)
|
8548
|
+
|
8549
|
+
def connect(self, tgt: ManageTarget) -> ta.AsyncContextManager[CommandExecutor]:
|
8550
|
+
return self.get_connector(type(tgt)).connect(tgt)
|
7179
8551
|
|
7180
8552
|
|
7181
8553
|
##
|
7182
8554
|
|
7183
8555
|
|
7184
|
-
|
7185
|
-
|
7186
|
-
|
7187
|
-
|
7188
|
-
|
7189
|
-
|
7190
|
-
inj.bind_array_type(CommandRegistration, CommandRegistrations),
|
8556
|
+
@dc.dataclass(frozen=True)
|
8557
|
+
class LocalManageTargetConnector(ManageTargetConnector):
|
8558
|
+
_local_executor: LocalCommandExecutor
|
8559
|
+
_in_process_connector: InProcessRemoteExecutionConnector
|
8560
|
+
_pyremote_connector: PyremoteRemoteExecutionConnector
|
8561
|
+
_bootstrap: MainBootstrap
|
7191
8562
|
|
7192
|
-
|
7193
|
-
|
8563
|
+
@contextlib.asynccontextmanager
|
8564
|
+
async def connect(self, tgt: ManageTarget) -> ta.AsyncGenerator[CommandExecutor, None]:
|
8565
|
+
lmt = check.isinstance(tgt, LocalManageTarget)
|
7194
8566
|
|
7195
|
-
|
7196
|
-
|
8567
|
+
if isinstance(lmt, InProcessManageTarget):
|
8568
|
+
imt = check.isinstance(lmt, InProcessManageTarget)
|
7197
8569
|
|
7198
|
-
|
8570
|
+
if imt.mode == InProcessManageTarget.Mode.DIRECT:
|
8571
|
+
yield self._local_executor
|
7199
8572
|
|
7200
|
-
|
7201
|
-
|
8573
|
+
elif imt.mode == InProcessManageTarget.Mode.FAKE_REMOTE:
|
8574
|
+
async with self._in_process_connector.connect() as rce:
|
8575
|
+
yield rce
|
7202
8576
|
|
7203
|
-
|
8577
|
+
else:
|
8578
|
+
raise TypeError(imt.mode)
|
8579
|
+
|
8580
|
+
elif isinstance(lmt, SubprocessManageTarget):
|
8581
|
+
async with self._pyremote_connector.connect(
|
8582
|
+
RemoteSpawning.Target(
|
8583
|
+
python=lmt.python,
|
8584
|
+
),
|
8585
|
+
self._bootstrap,
|
8586
|
+
) as rce:
|
8587
|
+
yield rce
|
7204
8588
|
|
7205
|
-
|
8589
|
+
else:
|
8590
|
+
raise TypeError(lmt)
|
7206
8591
|
|
7207
|
-
def provide_command_executor_map(
|
7208
|
-
injector: Injector,
|
7209
|
-
crs: CommandExecutorRegistrations,
|
7210
|
-
) -> CommandExecutorMap:
|
7211
|
-
dct: ta.Dict[ta.Type[Command], CommandExecutor] = {}
|
7212
8592
|
|
7213
|
-
|
7214
|
-
for cr in crs:
|
7215
|
-
if cr.command_cls in dct:
|
7216
|
-
raise KeyError(cr.command_cls)
|
8593
|
+
##
|
7217
8594
|
|
7218
|
-
factory = functools.partial(injector.provide, cr.executor_cls)
|
7219
|
-
if main_config.debug:
|
7220
|
-
ce = factory()
|
7221
|
-
else:
|
7222
|
-
ce = _FactoryCommandExecutor(factory)
|
7223
8595
|
|
7224
|
-
|
8596
|
+
@dc.dataclass(frozen=True)
|
8597
|
+
class DockerManageTargetConnector(ManageTargetConnector):
|
8598
|
+
_pyremote_connector: PyremoteRemoteExecutionConnector
|
8599
|
+
_bootstrap: MainBootstrap
|
7225
8600
|
|
7226
|
-
|
8601
|
+
@contextlib.asynccontextmanager
|
8602
|
+
async def connect(self, tgt: ManageTarget) -> ta.AsyncGenerator[CommandExecutor, None]:
|
8603
|
+
dmt = check.isinstance(tgt, DockerManageTarget)
|
8604
|
+
|
8605
|
+
sh_parts: ta.List[str] = ['docker']
|
8606
|
+
if dmt.image is not None:
|
8607
|
+
sh_parts.extend(['run', '-i', dmt.image])
|
8608
|
+
elif dmt.container_id is not None:
|
8609
|
+
sh_parts.extend(['exec', dmt.container_id])
|
8610
|
+
else:
|
8611
|
+
raise ValueError(dmt)
|
7227
8612
|
|
7228
|
-
|
7229
|
-
|
8613
|
+
async with self._pyremote_connector.connect(
|
8614
|
+
RemoteSpawning.Target(
|
8615
|
+
shell=' '.join(sh_parts),
|
8616
|
+
python=dmt.python,
|
8617
|
+
),
|
8618
|
+
self._bootstrap,
|
8619
|
+
) as rce:
|
8620
|
+
yield rce
|
7230
8621
|
|
7231
|
-
inj.bind(LocalCommandExecutor, singleton=True, eager=main_config.debug),
|
7232
|
-
])
|
7233
8622
|
|
7234
|
-
|
8623
|
+
##
|
7235
8624
|
|
7236
|
-
command_cls: ta.Any
|
7237
|
-
executor_cls: ta.Any
|
7238
|
-
for command_cls, executor_cls in [
|
7239
|
-
(SubprocessCommand, SubprocessCommandExecutor),
|
7240
|
-
(InterpCommand, InterpCommandExecutor),
|
7241
|
-
]:
|
7242
|
-
lst.append(bind_command(command_cls, executor_cls))
|
7243
8625
|
|
7244
|
-
|
8626
|
+
@dc.dataclass(frozen=True)
|
8627
|
+
class SshManageTargetConnector(ManageTargetConnector):
|
8628
|
+
_pyremote_connector: PyremoteRemoteExecutionConnector
|
8629
|
+
_bootstrap: MainBootstrap
|
7245
8630
|
|
7246
|
-
|
8631
|
+
@contextlib.asynccontextmanager
|
8632
|
+
async def connect(self, tgt: ManageTarget) -> ta.AsyncGenerator[CommandExecutor, None]:
|
8633
|
+
smt = check.isinstance(tgt, SshManageTarget)
|
8634
|
+
|
8635
|
+
sh_parts: ta.List[str] = ['ssh']
|
8636
|
+
if smt.key_file is not None:
|
8637
|
+
sh_parts.extend(['-i', smt.key_file])
|
8638
|
+
addr = check.not_none(smt.host)
|
8639
|
+
if smt.username is not None:
|
8640
|
+
addr = f'{smt.username}@{addr}'
|
8641
|
+
sh_parts.append(addr)
|
8642
|
+
|
8643
|
+
async with self._pyremote_connector.connect(
|
8644
|
+
RemoteSpawning.Target(
|
8645
|
+
shell=' '.join(sh_parts),
|
8646
|
+
shell_quote=True,
|
8647
|
+
python=smt.python,
|
8648
|
+
),
|
8649
|
+
self._bootstrap,
|
8650
|
+
) as rce:
|
8651
|
+
yield rce
|
7247
8652
|
|
7248
8653
|
|
7249
8654
|
########################################
|
7250
|
-
# ../deploy/
|
8655
|
+
# ../deploy/interp.py
|
7251
8656
|
|
7252
8657
|
|
7253
|
-
|
7254
|
-
) -> InjectorBindings:
|
7255
|
-
lst: ta.List[InjectorBindingOrBindings] = [
|
7256
|
-
bind_command(DeployCommand, DeployCommandExecutor),
|
7257
|
-
]
|
8658
|
+
##
|
7258
8659
|
|
7259
|
-
|
8660
|
+
|
8661
|
+
@dc.dataclass(frozen=True)
|
8662
|
+
class InterpCommand(Command['InterpCommand.Output']):
|
8663
|
+
spec: str
|
8664
|
+
install: bool = False
|
8665
|
+
|
8666
|
+
@dc.dataclass(frozen=True)
|
8667
|
+
class Output(Command.Output):
|
8668
|
+
exe: str
|
8669
|
+
version: str
|
8670
|
+
opts: InterpOpts
|
8671
|
+
|
8672
|
+
|
8673
|
+
class InterpCommandExecutor(CommandExecutor[InterpCommand, InterpCommand.Output]):
|
8674
|
+
async def execute(self, cmd: InterpCommand) -> InterpCommand.Output:
|
8675
|
+
i = InterpSpecifier.parse(check.not_none(cmd.spec))
|
8676
|
+
o = check.not_none(await DEFAULT_INTERP_RESOLVER.resolve(i, install=cmd.install))
|
8677
|
+
return InterpCommand.Output(
|
8678
|
+
exe=o.exe,
|
8679
|
+
version=str(o.version.version),
|
8680
|
+
opts=o.version.opts,
|
8681
|
+
)
|
7260
8682
|
|
7261
8683
|
|
7262
8684
|
########################################
|
7263
|
-
# ../
|
8685
|
+
# ../targets/inject.py
|
7264
8686
|
|
7265
8687
|
|
7266
|
-
def
|
7267
|
-
*,
|
7268
|
-
system_config: SystemConfig,
|
7269
|
-
) -> InjectorBindings:
|
8688
|
+
def bind_targets() -> InjectorBindings:
|
7270
8689
|
lst: ta.List[InjectorBindingOrBindings] = [
|
7271
|
-
inj.bind(
|
8690
|
+
inj.bind(LocalManageTargetConnector, singleton=True),
|
8691
|
+
inj.bind(DockerManageTargetConnector, singleton=True),
|
8692
|
+
inj.bind(SshManageTargetConnector, singleton=True),
|
8693
|
+
|
8694
|
+
inj.bind(TypeSwitchedManageTargetConnector, singleton=True),
|
8695
|
+
inj.bind(ManageTargetConnector, to_key=TypeSwitchedManageTargetConnector),
|
7272
8696
|
]
|
7273
8697
|
|
7274
8698
|
#
|
7275
8699
|
|
7276
|
-
|
7277
|
-
|
8700
|
+
def provide_manage_target_connector_map(injector: Injector) -> ManageTargetConnectorMap:
|
8701
|
+
return ManageTargetConnectorMap({
|
8702
|
+
LocalManageTarget: injector[LocalManageTargetConnector],
|
8703
|
+
DockerManageTarget: injector[DockerManageTargetConnector],
|
8704
|
+
SshManageTarget: injector[SshManageTargetConnector],
|
8705
|
+
})
|
8706
|
+
lst.append(inj.bind(provide_manage_target_connector_map, singleton=True))
|
7278
8707
|
|
7279
8708
|
#
|
7280
8709
|
|
7281
|
-
|
7282
|
-
lst.extend([
|
7283
|
-
inj.bind(AptSystemPackageManager, singleton=True),
|
7284
|
-
inj.bind(SystemPackageManager, to_key=AptSystemPackageManager),
|
7285
|
-
])
|
8710
|
+
return inj.as_bindings(*lst)
|
7286
8711
|
|
7287
|
-
elif platform == 'darwin':
|
7288
|
-
lst.extend([
|
7289
|
-
inj.bind(BrewSystemPackageManager, singleton=True),
|
7290
|
-
inj.bind(SystemPackageManager, to_key=BrewSystemPackageManager),
|
7291
|
-
])
|
7292
8712
|
|
7293
|
-
|
8713
|
+
########################################
|
8714
|
+
# ../deploy/inject.py
|
7294
8715
|
|
7295
|
-
lst.extend([
|
7296
|
-
bind_command(CheckSystemPackageCommand, CheckSystemPackageCommandExecutor),
|
7297
|
-
])
|
7298
8716
|
|
7299
|
-
|
8717
|
+
def bind_deploy(
|
8718
|
+
*,
|
8719
|
+
deploy_config: DeployConfig,
|
8720
|
+
) -> InjectorBindings:
|
8721
|
+
lst: ta.List[InjectorBindingOrBindings] = [
|
8722
|
+
inj.bind(deploy_config),
|
8723
|
+
|
8724
|
+
inj.bind(DeployAppManager, singleton=True),
|
8725
|
+
inj.bind(DeployGitManager, singleton=True),
|
8726
|
+
inj.bind(DeployVenvManager, singleton=True),
|
8727
|
+
|
8728
|
+
bind_command(DeployCommand, DeployCommandExecutor),
|
8729
|
+
bind_command(InterpCommand, InterpCommandExecutor),
|
8730
|
+
]
|
8731
|
+
|
8732
|
+
if (dh := deploy_config.deploy_home) is not None:
|
8733
|
+
dh = os.path.abspath(os.path.expanduser(dh))
|
8734
|
+
lst.append(inj.bind(dh, key=DeployHome))
|
7300
8735
|
|
7301
8736
|
return inj.as_bindings(*lst)
|
7302
8737
|
|
@@ -7310,9 +8745,13 @@ def bind_system(
|
|
7310
8745
|
|
7311
8746
|
def bind_main(
|
7312
8747
|
*,
|
7313
|
-
main_config: MainConfig,
|
7314
|
-
|
7315
|
-
|
8748
|
+
main_config: MainConfig = MainConfig(),
|
8749
|
+
|
8750
|
+
deploy_config: DeployConfig = DeployConfig(),
|
8751
|
+
remote_config: RemoteConfig = RemoteConfig(),
|
8752
|
+
system_config: SystemConfig = SystemConfig(),
|
8753
|
+
|
8754
|
+
main_bootstrap: ta.Optional[MainBootstrap] = None,
|
7316
8755
|
) -> InjectorBindings:
|
7317
8756
|
lst: ta.List[InjectorBindingOrBindings] = [
|
7318
8757
|
inj.bind(main_config),
|
@@ -7321,7 +8760,9 @@ def bind_main(
|
|
7321
8760
|
main_config=main_config,
|
7322
8761
|
),
|
7323
8762
|
|
7324
|
-
bind_deploy(
|
8763
|
+
bind_deploy(
|
8764
|
+
deploy_config=deploy_config,
|
8765
|
+
),
|
7325
8766
|
|
7326
8767
|
bind_remote(
|
7327
8768
|
remote_config=remote_config,
|
@@ -7330,10 +8771,17 @@ def bind_main(
|
|
7330
8771
|
bind_system(
|
7331
8772
|
system_config=system_config,
|
7332
8773
|
),
|
8774
|
+
|
8775
|
+
bind_targets(),
|
7333
8776
|
]
|
7334
8777
|
|
7335
8778
|
#
|
7336
8779
|
|
8780
|
+
if main_bootstrap is not None:
|
8781
|
+
lst.append(inj.bind(main_bootstrap))
|
8782
|
+
|
8783
|
+
#
|
8784
|
+
|
7337
8785
|
def build_obj_marshaler_manager(insts: ObjMarshalerInstallers) -> ObjMarshalerManager:
|
7338
8786
|
msh = ObjMarshalerManager()
|
7339
8787
|
inst: ObjMarshalerInstaller
|
@@ -7363,8 +8811,12 @@ def main_bootstrap(bs: MainBootstrap) -> Injector:
|
|
7363
8811
|
|
7364
8812
|
injector = inj.create_injector(bind_main( # noqa
|
7365
8813
|
main_config=bs.main_config,
|
8814
|
+
|
8815
|
+
deploy_config=bs.deploy_config,
|
7366
8816
|
remote_config=bs.remote_config,
|
7367
8817
|
system_config=bs.system_config,
|
8818
|
+
|
8819
|
+
main_bootstrap=bs,
|
7368
8820
|
))
|
7369
8821
|
|
7370
8822
|
return injector
|
@@ -7378,10 +8830,6 @@ class MainCli(ArgparseCli):
|
|
7378
8830
|
@argparse_command(
|
7379
8831
|
argparse_arg('--_payload-file'),
|
7380
8832
|
|
7381
|
-
argparse_arg('-s', '--shell'),
|
7382
|
-
argparse_arg('-q', '--shell-quote', action='store_true'),
|
7383
|
-
argparse_arg('--python', default='python3'),
|
7384
|
-
|
7385
8833
|
argparse_arg('--pycharm-debug-port', type=int),
|
7386
8834
|
argparse_arg('--pycharm-debug-host'),
|
7387
8835
|
argparse_arg('--pycharm-debug-version'),
|
@@ -7390,8 +8838,9 @@ class MainCli(ArgparseCli):
|
|
7390
8838
|
|
7391
8839
|
argparse_arg('--debug', action='store_true'),
|
7392
8840
|
|
7393
|
-
argparse_arg('--
|
8841
|
+
argparse_arg('--deploy-home'),
|
7394
8842
|
|
8843
|
+
argparse_arg('target'),
|
7395
8844
|
argparse_arg('command', nargs='+'),
|
7396
8845
|
)
|
7397
8846
|
async def run(self) -> None:
|
@@ -7402,6 +8851,10 @@ class MainCli(ArgparseCli):
|
|
7402
8851
|
debug=bool(self.args.debug),
|
7403
8852
|
),
|
7404
8853
|
|
8854
|
+
deploy_config=DeployConfig(
|
8855
|
+
deploy_home=self.args.deploy_home,
|
8856
|
+
),
|
8857
|
+
|
7405
8858
|
remote_config=RemoteConfig(
|
7406
8859
|
payload_file=self.args._payload_file, # noqa
|
7407
8860
|
|
@@ -7412,8 +8865,6 @@ class MainCli(ArgparseCli):
|
|
7412
8865
|
) if self.args.pycharm_debug_port is not None else None,
|
7413
8866
|
|
7414
8867
|
timebomb_delay_s=self.args.remote_timebomb_delay_s,
|
7415
|
-
|
7416
|
-
use_in_process_remote_executor=True,
|
7417
8868
|
),
|
7418
8869
|
)
|
7419
8870
|
|
@@ -7427,6 +8878,11 @@ class MainCli(ArgparseCli):
|
|
7427
8878
|
|
7428
8879
|
msh = injector[ObjMarshalerManager]
|
7429
8880
|
|
8881
|
+
ts = self.args.target
|
8882
|
+
if not ts.startswith('{'):
|
8883
|
+
ts = json.dumps({ts: {}})
|
8884
|
+
tgt: ManageTarget = msh.unmarshal_obj(json.loads(ts), ManageTarget)
|
8885
|
+
|
7430
8886
|
cmds: ta.List[Command] = []
|
7431
8887
|
cmd: Command
|
7432
8888
|
for c in self.args.command:
|
@@ -7437,21 +8893,7 @@ class MainCli(ArgparseCli):
|
|
7437
8893
|
|
7438
8894
|
#
|
7439
8895
|
|
7440
|
-
async with
|
7441
|
-
ce: CommandExecutor
|
7442
|
-
|
7443
|
-
if self.args.local:
|
7444
|
-
ce = injector[LocalCommandExecutor]
|
7445
|
-
|
7446
|
-
else:
|
7447
|
-
tgt = RemoteSpawning.Target(
|
7448
|
-
shell=self.args.shell,
|
7449
|
-
shell_quote=self.args.shell_quote,
|
7450
|
-
python=self.args.python,
|
7451
|
-
)
|
7452
|
-
|
7453
|
-
ce = await es.enter_async_context(injector[RemoteExecutionConnector].connect(tgt, bs)) # noqa
|
7454
|
-
|
8896
|
+
async with injector[ManageTargetConnector].connect(tgt) as ce:
|
7455
8897
|
async def run_command(cmd: Command) -> None:
|
7456
8898
|
res = await ce.try_execute(
|
7457
8899
|
cmd,
|