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,374 @@
1
+ import inspect
2
+ import logging
3
+ import os
4
+ import subprocess
5
+ from collections.abc import Generator
6
+ from pathlib import Path
7
+ from typing import Any, Literal, TypedDict
8
+
9
+ from localstack_cli import config, constants
10
+ from localstack_cli.utils.objects import singleton_factory
11
+ from localstack_cli.utils.venv import VirtualEnvironment
12
+ from plux import PluginManager
13
+
14
+ LOG = logging.getLogger(__name__)
15
+
16
+ LOCALSTACK_VENV = VirtualEnvironment(constants.LOCALSTACK_VENV_FOLDER)
17
+
18
+ VENV_DIRECTORY = "extensions/python_venv"
19
+
20
+
21
+ def get_extensions_venv() -> VirtualEnvironment:
22
+ """
23
+ Returns a VirtualEnvironment object point to ``<var_libs>/extensions/python_venv``, either on the host
24
+ or in the container.
25
+
26
+ :return: the virtual environment
27
+ """
28
+ return VirtualEnvironment(os.path.join(config.dirs.var_libs, VENV_DIRECTORY))
29
+
30
+
31
+ @singleton_factory
32
+ def get_extension_repository() -> "ExtensionsRepository":
33
+ """
34
+ Returns the global ExtensionRepository and ensures that the localstack extension venv was created
35
+ correctly.
36
+
37
+ :return: an ExtensionsRepository instance
38
+ """
39
+ return ExtensionsRepository(init_extension_venv())
40
+
41
+
42
+ def init_extension_venv() -> VirtualEnvironment:
43
+ """
44
+ Idempotent operation to ensure the extensions virtual environment is created, and the localstack venv
45
+ is linked into it via a .pth file.
46
+ """
47
+ venv = get_extensions_venv()
48
+
49
+ if not venv.exists:
50
+ LOG.info("initializing extension environment at %s", venv.venv_dir)
51
+ venv.create()
52
+ LOG.debug("adding localstack venv path %s to %s", LOCALSTACK_VENV, venv.venv_dir)
53
+ venv.add_pth("localstack-venv", LOCALSTACK_VENV)
54
+
55
+ return venv
56
+
57
+
58
+ def list_extension_metadata() -> list[dict]:
59
+ """
60
+ Lists all available extensions and their distribution metadata.
61
+
62
+ :return: list of metadata documents
63
+ """
64
+ from localstack_cli.extensions.api import Extension
65
+
66
+ return list_plugin_distribution_data(PluginManager(Extension.namespace))
67
+
68
+
69
+ def list_plugin_distribution_data(plugin_manager: PluginManager) -> list[dict]:
70
+ """
71
+ Returns a list of dictionaries containing plugin metadata.
72
+
73
+ :param plugin_manager: the plugin manager holding the plugins
74
+ :return: a list of metadata documents
75
+ """
76
+ metadata = []
77
+
78
+ for plugin in plugin_manager.list_containers():
79
+ try:
80
+ dist = plugin.distribution
81
+ except ValueError:
82
+ continue
83
+ except Exception as e:
84
+ LOG.error(
85
+ "Error while resolving distribution for plugin %s: %s. This probably means that the "
86
+ "package was removed or otherwise changed after the plugin was loaded. Restarting LocalStack "
87
+ "should fix the issue.",
88
+ plugin.name,
89
+ e,
90
+ )
91
+ continue
92
+ if not dist:
93
+ continue
94
+
95
+ spec = plugin.plugin_spec
96
+ module = inspect.getmodule(plugin.plugin_spec.factory)
97
+
98
+ doc = {
99
+ "namespace": spec.namespace,
100
+ "name": plugin.name,
101
+ "factory": {
102
+ "module": str(module.__name__),
103
+ "code": f"{module.__name__}.{spec.factory.__name__}",
104
+ "file": str(module.__file__),
105
+ },
106
+ "distribution": dist.metadata.json,
107
+ }
108
+ metadata.append(doc)
109
+
110
+ return metadata
111
+
112
+
113
+ class InstallerEvent(TypedDict, total=False):
114
+ event: Literal["status", "log", "error", "exception", "pip", "extension"]
115
+ message: str
116
+ extra: dict[str, Any] | None
117
+
118
+
119
+ class ExtensionsRepository:
120
+ """
121
+ Helper class around a VirtualEnvironment that can install and uninstall packages into the venv.
122
+ """
123
+
124
+ venv: VirtualEnvironment
125
+
126
+ def __init__(self, venv: VirtualEnvironment = None):
127
+ self.venv = venv or get_extensions_venv()
128
+ self.venv.inject_to_sys_path()
129
+
130
+ @property
131
+ def pip(self) -> Path:
132
+ """
133
+ Returns the path to the pip binary within the virtual environment, or raises a FileNotFoundError if it
134
+ does not exist.
135
+
136
+ :raises FileNotFoundError: if the pip binary wasn't found
137
+ :return: a path
138
+ """
139
+ # TODO: move to VirtualEnvironment
140
+ pip = self.venv.venv_dir / "bin" / "pip"
141
+ if not pip.exists():
142
+ raise FileNotFoundError(f"pip is not available at {self.pip}")
143
+ return pip
144
+
145
+ def pip_show(self, package: str) -> dict | None:
146
+ """
147
+ Runs `pip show <package>` in the virtual environment and returns the output as a dictionary if the
148
+ package exist, or None if it doesn't exist.
149
+
150
+ :param package: the package to look up
151
+ :return: the metadata of the package or None
152
+ """
153
+ # TODO: move to VirtualEnvironment
154
+ cmd = [self.pip, "show", package]
155
+ try:
156
+ output = subprocess.check_output(cmd, stderr=subprocess.STDOUT, text=True)
157
+ return dict(line.split(": ", maxsplit=1) for line in output.splitlines())
158
+ except subprocess.CalledProcessError as e:
159
+ if "not found" in e.output:
160
+ return None
161
+ raise
162
+
163
+ def run_install(self, name_or_url: str) -> Generator[InstallerEvent, None, None]:
164
+ """
165
+ Creates a new installer generator to install the given package or URL. The ``name_or_url``
166
+ parameter is used directly as input for ``pip install``. The generator yields ``InstallerEvents``
167
+ as the installation procedure progresses.
168
+
169
+ :param name_or_url: the pypi package name or URL to install
170
+ :return: a generator
171
+ """
172
+ cmd = [
173
+ self.pip,
174
+ "--no-input",
175
+ "--no-color",
176
+ "--disable-pip-version-check",
177
+ "install",
178
+ name_or_url,
179
+ ]
180
+
181
+ yield {"event": "status", "message": "Checking installed extensions"}
182
+
183
+ metadata = self.pip_show(name_or_url)
184
+ if metadata:
185
+ name = metadata["Name"]
186
+ summary = metadata["Summary"]
187
+ author = metadata["Author"]
188
+ yield {
189
+ "event": "log",
190
+ "message": f"Extension {name} ({summary} by {author}) already installed",
191
+ }
192
+ return
193
+
194
+ # load extensions that are installed now
195
+ _clear_plugin_cache()
196
+ before = {item["name"]: item for item in list_extension_metadata()}
197
+
198
+ yield {"event": "status", "message": "Installing extension"}
199
+
200
+ not_found = False
201
+ try:
202
+ with SubprocessLineStream.open(cmd) as stream:
203
+ for line in stream:
204
+ yield {"event": "pip", "message": line}
205
+ if "No matching distribution found for" in line:
206
+ not_found = True
207
+ yield {
208
+ "event": "error",
209
+ "message": f"Could not resolve package {name_or_url}, please check the URL or "
210
+ "that the package exists in pypi.",
211
+ }
212
+ except subprocess.CalledProcessError:
213
+ if not_found:
214
+ return
215
+ raise
216
+
217
+ # re-load all extension metadata and compare to the ones that were installed
218
+ _clear_plugin_cache()
219
+ after = {item["name"]: item for item in list_extension_metadata()}
220
+ installed = [v for k, v in after.items() if k not in before]
221
+
222
+ if installed:
223
+ yield {"event": "log", "message": "Extension successfully installed"}
224
+
225
+ for extension in installed:
226
+ yield {"event": "extension", "message": "", "extra": extension}
227
+ else:
228
+ yield {"event": "log", "message": "No change"}
229
+
230
+ yield {"event": "status", "message": "Extension installation completed"}
231
+
232
+ def run_uninstall(self, package: str) -> Generator[InstallerEvent, None, None]:
233
+ """
234
+ Like ``run_install``, only it performs a ``pip uninstall`` operation.
235
+
236
+ :param package: the package name
237
+ :return: a InstallerEvent generator
238
+ """
239
+ cmd = [
240
+ self.pip,
241
+ "--no-input",
242
+ "--no-color",
243
+ "--disable-pip-version-check",
244
+ "uninstall",
245
+ "-y",
246
+ package,
247
+ ]
248
+
249
+ yield {"event": "status", "message": "Checking extensions"}
250
+
251
+ metadata = self.pip_show(package)
252
+ if not metadata:
253
+ yield {"event": "log", "message": f"Extension {package} is not installed"}
254
+ return
255
+
256
+ name = metadata["Name"]
257
+ summary = metadata["Summary"]
258
+ yield {
259
+ "event": "log",
260
+ "message": f"Uninstalling extension {name} ({summary})",
261
+ }
262
+ yield {"event": "status", "message": "Uninstalling extension"}
263
+
264
+ with SubprocessLineStream.open(cmd) as stream:
265
+ for line in stream:
266
+ yield {"event": "pip", "message": line}
267
+
268
+ yield {"event": "log", "message": "Extension successfully uninstalled"}
269
+ _clear_plugin_cache()
270
+ yield {"event": "status", "message": "Extension uninstall completed"}
271
+
272
+
273
+ class SubprocessLineStream:
274
+ """
275
+ Class to help with the pattern of streaming the output of a subprocess line-by-line. A
276
+ SubprocessLineStream can be closed which will terminate the underlying process. The stream will
277
+ automatically wait for and terminate the command after EOF is reached. It also automatically detects
278
+ text mode and rstrips newlines.
279
+
280
+ TODO: move into run utils in LocalStack
281
+
282
+ Best enjoyed as follows::
283
+
284
+ with SubprocessLineStream.open(["cowsay", "hello"]) as stream:
285
+ for line in stream:
286
+ print("|", line)
287
+
288
+ print(stream.process.returncode)
289
+
290
+ Will print::
291
+
292
+ | _______
293
+ | < hello >
294
+ | -------
295
+ | \\ ^__^
296
+ | \\ (oo)\\_______
297
+ | (__)\\ )\\/\
298
+ | ||----w |
299
+ | || ||
300
+ 0
301
+ """
302
+
303
+ default_timeout: int = 5
304
+ """Time in seconds the stream will wait for the process output to finish before moving on."""
305
+
306
+ def __init__(self, process: subprocess.Popen):
307
+ self.process = process
308
+
309
+ def __iter__(self):
310
+ return self._gen()
311
+
312
+ def _gen(self):
313
+ stream = self.process.stdout
314
+
315
+ if self.process.text_mode:
316
+ newlines = "\r\n"
317
+ else:
318
+ newlines = b"\r\n"
319
+
320
+ for line in stream:
321
+ yield line.rstrip(newlines)
322
+
323
+ if self.process.wait(self.default_timeout) != 0:
324
+ raise subprocess.CalledProcessError(
325
+ returncode=self.process.returncode,
326
+ cmd=self.process.args,
327
+ )
328
+
329
+ def __enter__(self):
330
+ return self
331
+
332
+ def __exit__(self, exc_type, exc_val, exc_tb):
333
+ self.close()
334
+
335
+ def close(self):
336
+ self.process.terminate()
337
+
338
+ @classmethod
339
+ def open(cls, cmd, *args, **kwargs):
340
+ """
341
+ Creates a new subprocess.Popen object with the given args with some sane defaults (mapping stderr to
342
+ stdout, and using text=True), wrapped in this class.
343
+
344
+ :param cmd: the command to run
345
+ :param args: args passed to subprocess.Popen
346
+ :param kwargs: keyword args passed to subprocess.Popen
347
+ :return:
348
+ """
349
+ return cls(
350
+ subprocess.Popen(
351
+ cmd,
352
+ *args,
353
+ stdout=subprocess.PIPE,
354
+ stderr=subprocess.STDOUT,
355
+ text=True,
356
+ **kwargs,
357
+ )
358
+ )
359
+
360
+
361
+ def _clear_plugin_cache():
362
+ """Clears the underlying caches used by plux to resolve entry points and distribution metadata."""
363
+ # FIXME: this is necessary to re-load the entrypoint cache when trying to resolve extension
364
+ # before/after install, but bad because it leaks internals.
365
+ from plux.runtime.cache import EntryPointsCache
366
+ from plux.runtime.metadata import packages_distributions
367
+
368
+ # plux cache that stores resolved entry points. this will force a re-calculation of the path hashing.
369
+ cache = EntryPointsCache.instance()
370
+ with cache._lock:
371
+ cache._cache.clear()
372
+
373
+ # plux cache that stores distribution data from importlib.metadata
374
+ packages_distributions.cache_clear()