playground-ls-cli 4.14.1.dev8__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.
Files changed (112) hide show
  1. localstack_cli/__init__.py +0 -0
  2. localstack_cli/cli/__init__.py +10 -0
  3. localstack_cli/cli/console.py +11 -0
  4. localstack_cli/cli/core_plugin.py +12 -0
  5. localstack_cli/cli/exceptions.py +19 -0
  6. localstack_cli/cli/localstack.py +951 -0
  7. localstack_cli/cli/lpm.py +138 -0
  8. localstack_cli/cli/main.py +22 -0
  9. localstack_cli/cli/plugin.py +39 -0
  10. localstack_cli/cli/plugins.py +134 -0
  11. localstack_cli/cli/profiles.py +65 -0
  12. localstack_cli/config.py +1689 -0
  13. localstack_cli/constants.py +165 -0
  14. localstack_cli/logging/__init__.py +0 -0
  15. localstack_cli/logging/format.py +194 -0
  16. localstack_cli/logging/setup.py +142 -0
  17. localstack_cli/packages/__init__.py +25 -0
  18. localstack_cli/packages/api.py +418 -0
  19. localstack_cli/packages/core.py +416 -0
  20. localstack_cli/pro/__init__.py +0 -0
  21. localstack_cli/pro/core/__init__.py +0 -0
  22. localstack_cli/pro/core/bootstrap/__init__.py +1 -0
  23. localstack_cli/pro/core/bootstrap/auth.py +213 -0
  24. localstack_cli/pro/core/bootstrap/dns_utils.py +55 -0
  25. localstack_cli/pro/core/bootstrap/entitlements.py +117 -0
  26. localstack_cli/pro/core/bootstrap/extensions/__init__.py +3 -0
  27. localstack_cli/pro/core/bootstrap/extensions/__main__.py +106 -0
  28. localstack_cli/pro/core/bootstrap/extensions/autoinstall.py +63 -0
  29. localstack_cli/pro/core/bootstrap/extensions/bootstrap.py +97 -0
  30. localstack_cli/pro/core/bootstrap/extensions/repository.py +374 -0
  31. localstack_cli/pro/core/bootstrap/licensingv2.py +1259 -0
  32. localstack_cli/pro/core/bootstrap/pods/__init__.py +0 -0
  33. localstack_cli/pro/core/bootstrap/pods/api_types.py +17 -0
  34. localstack_cli/pro/core/bootstrap/pods/constants.py +26 -0
  35. localstack_cli/pro/core/bootstrap/pods/remotes/__init__.py +0 -0
  36. localstack_cli/pro/core/bootstrap/pods/remotes/api.py +75 -0
  37. localstack_cli/pro/core/bootstrap/pods/remotes/configs.py +69 -0
  38. localstack_cli/pro/core/bootstrap/pods/remotes/params.py +86 -0
  39. localstack_cli/pro/core/bootstrap/pods_client.py +834 -0
  40. localstack_cli/pro/core/cli/__init__.py +0 -0
  41. localstack_cli/pro/core/cli/auth.py +226 -0
  42. localstack_cli/pro/core/cli/aws.py +16 -0
  43. localstack_cli/pro/core/cli/cli.py +99 -0
  44. localstack_cli/pro/core/cli/click_utils.py +21 -0
  45. localstack_cli/pro/core/cli/cloud_pods.py +465 -0
  46. localstack_cli/pro/core/cli/diff_view.py +41 -0
  47. localstack_cli/pro/core/cli/ephemeral.py +199 -0
  48. localstack_cli/pro/core/cli/extensions.py +492 -0
  49. localstack_cli/pro/core/cli/iam.py +180 -0
  50. localstack_cli/pro/core/cli/license.py +90 -0
  51. localstack_cli/pro/core/cli/localstack.py +118 -0
  52. localstack_cli/pro/core/cli/replicator.py +378 -0
  53. localstack_cli/pro/core/cli/state.py +183 -0
  54. localstack_cli/pro/core/cli/tree_view.py +235 -0
  55. localstack_cli/pro/core/config.py +556 -0
  56. localstack_cli/pro/core/constants.py +54 -0
  57. localstack_cli/pro/core/plugins.py +169 -0
  58. localstack_cli/runtime/__init__.py +6 -0
  59. localstack_cli/runtime/exceptions.py +7 -0
  60. localstack_cli/runtime/hooks.py +73 -0
  61. localstack_cli/testing/__init__.py +1 -0
  62. localstack_cli/testing/config.py +4 -0
  63. localstack_cli/utils/__init__.py +0 -0
  64. localstack_cli/utils/analytics/__init__.py +12 -0
  65. localstack_cli/utils/analytics/cli.py +67 -0
  66. localstack_cli/utils/analytics/client.py +111 -0
  67. localstack_cli/utils/analytics/events.py +30 -0
  68. localstack_cli/utils/analytics/logger.py +48 -0
  69. localstack_cli/utils/analytics/metadata.py +250 -0
  70. localstack_cli/utils/analytics/publisher.py +160 -0
  71. localstack_cli/utils/analytics/service_request_aggregator.py +133 -0
  72. localstack_cli/utils/archives.py +271 -0
  73. localstack_cli/utils/batching.py +258 -0
  74. localstack_cli/utils/bootstrap.py +1418 -0
  75. localstack_cli/utils/checksum.py +313 -0
  76. localstack_cli/utils/collections.py +554 -0
  77. localstack_cli/utils/common.py +229 -0
  78. localstack_cli/utils/container_networking.py +142 -0
  79. localstack_cli/utils/container_utils/__init__.py +0 -0
  80. localstack_cli/utils/container_utils/container_client.py +1585 -0
  81. localstack_cli/utils/container_utils/docker_cmd_client.py +987 -0
  82. localstack_cli/utils/container_utils/docker_sdk_client.py +1018 -0
  83. localstack_cli/utils/crypto.py +294 -0
  84. localstack_cli/utils/docker_utils.py +272 -0
  85. localstack_cli/utils/files.py +327 -0
  86. localstack_cli/utils/functions.py +92 -0
  87. localstack_cli/utils/http.py +326 -0
  88. localstack_cli/utils/json.py +219 -0
  89. localstack_cli/utils/net.py +516 -0
  90. localstack_cli/utils/no_exit_argument_parser.py +19 -0
  91. localstack_cli/utils/numbers.py +49 -0
  92. localstack_cli/utils/objects.py +235 -0
  93. localstack_cli/utils/patch.py +260 -0
  94. localstack_cli/utils/platform.py +77 -0
  95. localstack_cli/utils/run.py +514 -0
  96. localstack_cli/utils/server/__init__.py +0 -0
  97. localstack_cli/utils/server/tcp_proxy.py +108 -0
  98. localstack_cli/utils/serving.py +187 -0
  99. localstack_cli/utils/ssl.py +71 -0
  100. localstack_cli/utils/strings.py +245 -0
  101. localstack_cli/utils/sync.py +267 -0
  102. localstack_cli/utils/threads.py +163 -0
  103. localstack_cli/utils/time.py +81 -0
  104. localstack_cli/utils/urls.py +21 -0
  105. localstack_cli/utils/venv.py +100 -0
  106. localstack_cli/utils/xml.py +41 -0
  107. localstack_cli/version.py +34 -0
  108. playground_ls_cli-4.14.1.dev8.dist-info/METADATA +95 -0
  109. playground_ls_cli-4.14.1.dev8.dist-info/RECORD +112 -0
  110. playground_ls_cli-4.14.1.dev8.dist-info/WHEEL +5 -0
  111. playground_ls_cli-4.14.1.dev8.dist-info/entry_points.txt +17 -0
  112. playground_ls_cli-4.14.1.dev8.dist-info/top_level.txt +1 -0
@@ -0,0 +1,418 @@
1
+ from __future__ import annotations
2
+
3
+ import abc
4
+ import functools
5
+ import logging
6
+ import os
7
+ from collections import defaultdict
8
+ from collections.abc import Callable
9
+ from enum import Enum
10
+ from inspect import getmodule
11
+ from threading import Lock, RLock
12
+ from typing import Any, Generic, ParamSpec, TypeVar
13
+
14
+ from plux import Plugin, PluginManager, PluginSpec # type: ignore
15
+
16
+ from localstack_cli import config
17
+
18
+ LOG = logging.getLogger(__name__)
19
+
20
+
21
+ class PackageException(Exception):
22
+ """Basic exception indicating that a package-specific exception occurred."""
23
+
24
+ pass
25
+
26
+
27
+ class NoSuchVersionException(PackageException):
28
+ """Exception indicating that a requested installer version is not available / supported."""
29
+
30
+ def __init__(self, package: str | None = None, version: str | None = None):
31
+ message = "Unable to find requested version"
32
+ if package and version:
33
+ message += f"Unable to find requested version '{version}' for package '{package}'"
34
+ super().__init__(message)
35
+
36
+
37
+ class InstallTarget(Enum):
38
+ """
39
+ Different installation targets.
40
+ Attention:
41
+ - These targets are directly used in the LPM API and are therefore part of a public API!
42
+ - The order of the entries in the enum define the default lookup order when looking for package installations.
43
+
44
+ These targets refer to the directories in config#Directories.
45
+ - VAR_LIBS: Used for packages installed at runtime. They are installed in a host-mounted volume.
46
+ This directory / these installations persist across multiple containers.
47
+ - STATIC_LIBS: Used for packages installed at build time. They are installed in a non-host-mounted volume.
48
+ This directory is re-created whenever a container is recreated.
49
+ """
50
+
51
+ VAR_LIBS = config.dirs.var_libs
52
+ STATIC_LIBS = config.dirs.static_libs
53
+
54
+
55
+ class PackageInstaller(abc.ABC):
56
+ """
57
+ Base class for a specific installer.
58
+ An instance of an installer manages the installation of a specific Package (in a specific version, if there are
59
+ multiple versions).
60
+ """
61
+
62
+ def __init__(self, name: str, version: str, install_lock: Lock | None = None):
63
+ """
64
+ :param name: technical package name, f.e. "opensearch"
65
+ :param version: version of the package to install
66
+ :param install_lock: custom lock which should be used for this package installer instance for the
67
+ complete #install call. Defaults to a per-instance reentrant lock (RLock).
68
+ Package instances create one installer per version. Therefore, by default, the lock
69
+ ensures that package installations of the same package and version are mutually exclusive.
70
+ """
71
+ self.name = name
72
+ self.version = version
73
+ self.install_lock = install_lock or RLock()
74
+ self._setup_for_target: dict[InstallTarget, bool] = defaultdict(lambda: False)
75
+
76
+ def install(self, target: InstallTarget | None = None) -> None:
77
+ """
78
+ Performs the package installation.
79
+
80
+ :param target: preferred installation target. Default is VAR_LIBS.
81
+ :return: None
82
+ :raises PackageException: if the installation fails
83
+ """
84
+ try:
85
+ if not target:
86
+ target = InstallTarget.VAR_LIBS
87
+ # We have to acquire the lock before checking if the package is installed, as the is_installed check
88
+ # is _only_ reliable if no other thread is currently actually installing
89
+ with self.install_lock:
90
+ # Skip the installation if it's already installed
91
+ if not self.is_installed():
92
+ LOG.debug("Starting installation of %s %s...", self.name, self.version)
93
+ self._prepare_installation(target)
94
+ self._install(target)
95
+ self._post_process(target)
96
+ LOG.debug("Installation of %s %s finished.", self.name, self.version)
97
+ else:
98
+ LOG.debug(
99
+ "Installation of %s %s skipped (already installed).",
100
+ self.name,
101
+ self.version,
102
+ )
103
+ if not self._setup_for_target[target]:
104
+ LOG.debug("Performing runtime setup for already installed package.")
105
+ self._setup_existing_installation(target)
106
+ except PackageException as e:
107
+ raise e
108
+ except Exception as e:
109
+ raise PackageException(f"Installation of {self.name} {self.version} failed.") from e
110
+
111
+ def is_installed(self) -> bool:
112
+ """
113
+ Checks if the package is already installed.
114
+
115
+ :return: True if the package is already installed (i.e. an installation is not necessary).
116
+ """
117
+ return self.get_installed_dir() is not None
118
+
119
+ def get_installed_dir(self) -> str | None:
120
+ """
121
+ Returns the directory of an existing installation. The directory can differ based on the installation target
122
+ and version.
123
+ :return: str representation of the installation directory path or None if the package is not installed anywhere
124
+ """
125
+ for target in InstallTarget:
126
+ directory = self._get_install_dir(target)
127
+ if directory and os.path.exists(self._get_install_marker_path(directory)):
128
+ return directory
129
+ return None
130
+
131
+ def _get_install_dir(self, target: InstallTarget) -> str:
132
+ """
133
+ Builds the installation directory for a specific target.
134
+ :param target: to create the installation directory path for
135
+ :return: str representation of the installation directory for the given target
136
+ """
137
+ return os.path.join(target.value, self.name, self.version)
138
+
139
+ def _get_install_marker_path(self, install_dir: str) -> str:
140
+ """
141
+ Builds the path for a specific "marker" whose presence indicates that the package has been installed
142
+ successfully in the given directory.
143
+
144
+ :param install_dir: base path for the check (f.e. /var/lib/localstack/lib/dynamodblocal/latest/)
145
+ :return: path which should be checked to indicate if the package has been installed successfully
146
+ (f.e. /var/lib/localstack/lib/dynamodblocal/latest/DynamoDBLocal.jar)
147
+ """
148
+ raise NotImplementedError()
149
+
150
+ def _setup_existing_installation(self, target: InstallTarget) -> None:
151
+ """
152
+ Internal function to perform the setup for an existing installation, f.e. adding a path to an environment.
153
+ This is only necessary for certain installers (like the PythonPackageInstaller).
154
+ This function will _always_ be executed _exactly_ once within a Python session for a specific installer
155
+ instance and target, if #install is called for the respective target.
156
+ :param target: of the installation
157
+ :return: None
158
+ """
159
+ pass
160
+
161
+ def _prepare_installation(self, target: InstallTarget) -> None:
162
+ """
163
+ Internal function to prepare an installation, f.e. by downloading some data or installing an OS package repo.
164
+ Can be implemented by specific installers.
165
+ :param target: of the installation
166
+ :return: None
167
+ """
168
+ pass
169
+
170
+ def _install(self, target: InstallTarget) -> None:
171
+ """
172
+ Internal function to perform the actual installation.
173
+ Must be implemented by specific installers.
174
+ :param target: of the installation
175
+ :return: None
176
+ """
177
+ raise NotImplementedError()
178
+
179
+ def _post_process(self, target: InstallTarget) -> None:
180
+ """
181
+ Internal function to perform some post-processing, f.e. patching an installation or creating symlinks.
182
+ :param target: of the installation
183
+ :return: None
184
+ """
185
+ pass
186
+
187
+
188
+ # With Python 3.13 we should be able to set PackageInstaller as the default
189
+ # https://typing.python.org/en/latest/spec/generics.html#type-parameter-defaults
190
+ T = TypeVar("T", bound=PackageInstaller)
191
+
192
+
193
+ class Package(abc.ABC, Generic[T]):
194
+ """
195
+ A Package defines a specific kind of software, mostly used as backends or supporting system for service
196
+ implementations.
197
+ """
198
+
199
+ def __init__(self, name: str, default_version: str):
200
+ """
201
+ :param name: Human readable name of the package, f.e. "PostgreSQL"
202
+ :param default_version: Default version of the package which is used for installations if no version is defined
203
+ """
204
+ self.name = name
205
+ self.default_version = default_version
206
+
207
+ def get_installed_dir(self, version: str | None = None) -> str | None:
208
+ """
209
+ Finds a directory where the package (in the specific version) is installed.
210
+ :param version: of the package to look for. If None, the default version of the package is used.
211
+ :return: str representation of the path to the existing installation directory or None if the package in this
212
+ version is not yet installed.
213
+ """
214
+ return self.get_installer(version).get_installed_dir()
215
+
216
+ def install(self, version: str | None = None, target: InstallTarget | None = None) -> None:
217
+ """
218
+ Installs the package in the given version in the preferred target location.
219
+ :param version: version of the package to install. If None, the default version of the package will be used.
220
+ :param target: preferred installation target. If None, the var_libs directory is used.
221
+ :raises NoSuchVersionException: If the given version is not supported.
222
+ """
223
+ self.get_installer(version).install(target)
224
+
225
+ @functools.lru_cache
226
+ def get_installer(self, version: str | None = None) -> T:
227
+ """
228
+ Returns the installer instance for a specific version of the package.
229
+
230
+ It is important that this be LRU cached. Installers have a mutex lock to prevent races, and it is necessary
231
+ that this method returns the same installer instance for a given version.
232
+
233
+ :param version: version of the package to install. If None, the default version of the package will be used.
234
+ :return: PackageInstaller instance for the given version.
235
+ :raises NoSuchVersionException: If the given version is not supported.
236
+ """
237
+ if not version:
238
+ return self.get_installer(self.default_version)
239
+ if version not in self.get_versions():
240
+ raise NoSuchVersionException(package=self.name, version=version)
241
+ return self._get_installer(version)
242
+
243
+ def get_versions(self) -> list[str]:
244
+ """
245
+ :return: List of all versions available for this package.
246
+ """
247
+ raise NotImplementedError()
248
+
249
+ def _get_installer(self, version: str) -> T:
250
+ """
251
+ Internal lookup function which needs to be implemented by specific packages.
252
+ It creates PackageInstaller instances for the specific version.
253
+
254
+ :param version: to find the installer for
255
+ :return: PackageInstaller instance responsible for installing the given version of the package.
256
+ """
257
+ raise NotImplementedError()
258
+
259
+ def __str__(self) -> str:
260
+ return self.name
261
+
262
+
263
+ class MultiPackageInstaller(PackageInstaller):
264
+ """
265
+ PackageInstaller implementation which composes of multiple package installers.
266
+ """
267
+
268
+ def __init__(self, name: str, version: str, package_installer: list[PackageInstaller]):
269
+ """
270
+ :param name: of the (multi-)package installer
271
+ :param version: of this (multi-)package installer
272
+ :param package_installer: List of installers this multi-package installer consists of
273
+ """
274
+ super().__init__(name=name, version=version)
275
+
276
+ assert isinstance(package_installer, list)
277
+ assert len(package_installer) > 0
278
+ self.package_installer = package_installer
279
+
280
+ def install(self, target: InstallTarget | None = None) -> None:
281
+ """
282
+ Installs the different packages this installer is composed of.
283
+
284
+ :param target: which defines where to install the packages.
285
+ :return: None
286
+ """
287
+ for package_installer in self.package_installer:
288
+ package_installer.install(target=target)
289
+
290
+ def get_installed_dir(self) -> str | None:
291
+ # By default, use the installed-dir of the first package
292
+ return self.package_installer[0].get_installed_dir()
293
+
294
+ def _install(self, target: InstallTarget) -> None:
295
+ # This package installer actually only calls other installers, we pass here
296
+ pass
297
+
298
+ def _get_install_dir(self, target: InstallTarget) -> str:
299
+ # By default, use the install-dir of the first package
300
+ return self.package_installer[0]._get_install_dir(target)
301
+
302
+ def _get_install_marker_path(self, install_dir: str) -> str:
303
+ # By default, use the install-marker-path of the first package
304
+ return self.package_installer[0]._get_install_marker_path(install_dir)
305
+
306
+
307
+ PLUGIN_NAMESPACE = "localstack.packages"
308
+
309
+
310
+ class PackagesPlugin(Plugin): # type: ignore[misc]
311
+ """
312
+ Plugin implementation for Package plugins.
313
+ A package plugin exposes a specific package instance.
314
+ """
315
+
316
+ api: str
317
+ name: str
318
+
319
+ def __init__(
320
+ self,
321
+ name: str,
322
+ scope: str,
323
+ get_package: Callable[[], Package[PackageInstaller] | list[Package[PackageInstaller]]],
324
+ should_load: Callable[[], bool] | None = None,
325
+ ) -> None:
326
+ super().__init__()
327
+ self.name = name
328
+ self.scope = scope
329
+ self._get_package = get_package
330
+ self._should_load = should_load
331
+
332
+ def should_load(self) -> bool:
333
+ if self._should_load:
334
+ return self._should_load()
335
+ return True
336
+
337
+ def get_package(self) -> Package[PackageInstaller]:
338
+ """
339
+ :return: returns the package instance of this package plugin
340
+ """
341
+ return self._get_package() # type: ignore[return-value]
342
+
343
+
344
+ class NoSuchPackageException(PackageException):
345
+ """Exception raised by the PackagesPluginManager to indicate that a package / version is not available."""
346
+
347
+ pass
348
+
349
+
350
+ class PackagesPluginManager(PluginManager[PackagesPlugin]): # type: ignore[misc]
351
+ """PluginManager which simplifies the loading / access of PackagesPlugins and their exposed package instances."""
352
+
353
+ def __init__(self) -> None:
354
+ super().__init__(PLUGIN_NAMESPACE)
355
+
356
+ def get_all_packages(self) -> list[tuple[str, str, Package[PackageInstaller]]]:
357
+ return sorted(
358
+ [(plugin.name, plugin.scope, plugin.get_package()) for plugin in self.load_all()]
359
+ )
360
+
361
+ def get_packages(
362
+ self, package_names: list[str], version: str | None = None
363
+ ) -> list[Package[PackageInstaller]]:
364
+ # Plugin names are unique, but there could be multiple packages with the same name in different scopes
365
+ plugin_specs_per_name = defaultdict(list)
366
+ # Plugin names have the format "<package-name>/<scope>", build a dict of specs per package name for the lookup
367
+ for plugin_spec in self.list_plugin_specs():
368
+ (package_name, _, _) = plugin_spec.name.rpartition("/")
369
+ plugin_specs_per_name[package_name].append(plugin_spec)
370
+
371
+ package_instances: list[Package[PackageInstaller]] = []
372
+ for package_name in package_names:
373
+ plugin_specs = plugin_specs_per_name.get(package_name)
374
+ if not plugin_specs:
375
+ raise NoSuchPackageException(
376
+ f"unable to locate installer for package {package_name}"
377
+ )
378
+ for plugin_spec in plugin_specs:
379
+ package_instance = self.load(plugin_spec.name).get_package()
380
+ package_instances.append(package_instance)
381
+ if version and version not in package_instance.get_versions():
382
+ raise NoSuchPackageException(
383
+ f"unable to locate installer for package {package_name} and version {version}"
384
+ )
385
+
386
+ return package_instances
387
+
388
+
389
+ P = ParamSpec("P")
390
+ T2 = TypeVar("T2")
391
+
392
+
393
+ def package(
394
+ name: str | None = None,
395
+ scope: str = "community",
396
+ should_load: Callable[[], bool] | None = None,
397
+ ) -> Callable[[Callable[[], Package[Any] | list[Package[Any]]]], PluginSpec]:
398
+ """
399
+ Decorator for marking methods that create Package instances as a PackagePlugin.
400
+ Methods marked with this decorator are discoverable as a PluginSpec within the namespace "localstack.packages",
401
+ with the name "<name>:<scope>". If api is not explicitly specified, then the parent module name is used as
402
+ service name.
403
+ """
404
+
405
+ def wrapper(fn: Callable[[], Package[Any] | list[Package[Any]]]) -> PluginSpec:
406
+ _name = name or getmodule(fn).__name__.split(".")[-2] # type: ignore[union-attr]
407
+
408
+ @functools.wraps(fn)
409
+ def factory() -> PackagesPlugin:
410
+ return PackagesPlugin(name=_name, scope=scope, get_package=fn, should_load=should_load)
411
+
412
+ return PluginSpec(PLUGIN_NAMESPACE, f"{_name}/{scope}", factory=factory)
413
+
414
+ return wrapper
415
+
416
+
417
+ # TODO remove (only used for migrating to new #package decorator)
418
+ packages = package