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,327 @@
1
+ import configparser
2
+ import inspect
3
+ import logging
4
+ import os
5
+ import shutil
6
+ import stat
7
+ import tempfile
8
+ from pathlib import Path
9
+
10
+ LOG = logging.getLogger(__name__)
11
+ TMP_FILES = []
12
+
13
+
14
+ def parse_config_file(file_or_str: str, single_section: bool = True) -> dict:
15
+ """Parse the given properties config file/string and return a dict of section->key->value.
16
+ If the config contains a single section, and 'single_section' is True, returns"""
17
+
18
+ config = configparser.RawConfigParser()
19
+
20
+ if os.path.exists(file_or_str):
21
+ file_or_str = load_file(file_or_str)
22
+
23
+ try:
24
+ config.read_string(file_or_str)
25
+ except configparser.MissingSectionHeaderError:
26
+ file_or_str = f"[default]\n{file_or_str}"
27
+ config.read_string(file_or_str)
28
+
29
+ sections = list(config.sections())
30
+
31
+ result = {sec: dict(config.items(sec)) for sec in sections}
32
+ if len(sections) == 1 and single_section:
33
+ result = result[sections[0]]
34
+
35
+ return result
36
+
37
+
38
+ def get_user_cache_dir() -> Path:
39
+ """
40
+ Returns the path of the user's cache dir (e.g., ~/.cache on Linux, or ~/Library/Caches on Mac).
41
+
42
+ :return: a Path pointing to the platform-specific cache dir of the user
43
+ """
44
+ from localstack_cli.utils.platform import is_linux, is_mac_os, is_windows
45
+
46
+ if is_windows():
47
+ return Path(os.path.expandvars(r"%LOCALAPPDATA%\cache"))
48
+ if is_mac_os():
49
+ return Path.home() / "Library" / "Caches"
50
+ if is_linux():
51
+ string_path = os.environ.get("XDG_CACHE_HOME")
52
+ if string_path and os.path.isabs(string_path):
53
+ return Path(string_path)
54
+ # Use the common place to store caches in Linux as a default
55
+ return Path.home() / ".cache"
56
+
57
+
58
+ def cache_dir() -> Path:
59
+ """
60
+ Returns the cache dir for localstack (e.g., ~/.cache/localstack)
61
+
62
+ :return: a Path pointing to the localstack cache dir
63
+ """
64
+ return get_user_cache_dir() / "localstack"
65
+
66
+
67
+ def save_file(file, content, append=False, permissions=None):
68
+ mode = "a" if append else "w+"
69
+ if not isinstance(content, str):
70
+ mode = mode + "b"
71
+
72
+ def _opener(path, flags):
73
+ return os.open(path, flags, permissions)
74
+
75
+ # make sure that the parent dir exists
76
+ mkdir(os.path.dirname(file))
77
+ # store file contents
78
+ with open(file, mode, opener=_opener if permissions else None) as f:
79
+ f.write(content)
80
+ f.flush()
81
+
82
+
83
+ def load_file(
84
+ file_path: str | os.PathLike,
85
+ default: str | bytes | None = None,
86
+ mode: str | None = None,
87
+ strict: bool = False,
88
+ ) -> str | bytes | None:
89
+ """
90
+ Return file contents
91
+
92
+ :param file_path: path of the file
93
+ :param default: if strict=False then return this value if the file does not exist
94
+ :param mode: mode to open the file with (e.g. `r`, `rw`)
95
+ :param strict: raise an error if the file path is not a file
96
+ :return: the file contents
97
+ """
98
+ if not os.path.isfile(file_path):
99
+ if strict:
100
+ raise FileNotFoundError(file_path)
101
+ else:
102
+ return default
103
+ if not mode:
104
+ mode = "r"
105
+ with open(file_path, mode) as f:
106
+ result = f.read()
107
+ return result
108
+
109
+
110
+ def get_or_create_file(file_path, content=None, permissions=None):
111
+ if os.path.exists(file_path):
112
+ return load_file(file_path)
113
+ content = "{}" if content is None else content
114
+ try:
115
+ save_file(file_path, content, permissions=permissions)
116
+ return content
117
+ except Exception:
118
+ pass
119
+
120
+
121
+ def replace_in_file(search, replace, file_path):
122
+ """Replace all occurrences of `search` with `replace` in the given file (overwrites in place!)"""
123
+ content = load_file(file_path) or ""
124
+ content_new = content.replace(search, replace)
125
+ if content != content_new:
126
+ save_file(file_path, content_new)
127
+
128
+
129
+ def mkdir(folder: str):
130
+ if not os.path.exists(folder):
131
+ os.makedirs(folder, exist_ok=True)
132
+
133
+
134
+ def is_empty_dir(directory: str, ignore_hidden: bool = False) -> bool:
135
+ """Return whether the given directory contains any entries (files/folders), including hidden
136
+ entries whose name starts with a dot (.), unless ignore_hidden=True is passed."""
137
+ if not os.path.isdir(directory):
138
+ raise Exception(f"Path is not a directory: {directory}")
139
+ entries = os.listdir(directory)
140
+ if ignore_hidden:
141
+ entries = [e for e in entries if not e.startswith(".")]
142
+ return not bool(entries)
143
+
144
+
145
+ def ensure_readable(file_path: str, default_perms: int = None):
146
+ if default_perms is None:
147
+ default_perms = 0o644
148
+ try:
149
+ with open(file_path, "rb"):
150
+ pass
151
+ except Exception:
152
+ LOG.info("Updating permissions as file is currently not readable: %s", file_path)
153
+ os.chmod(file_path, default_perms)
154
+
155
+
156
+ def chown_r(path: str, user: str):
157
+ """Recursive chown on the given file/directory path."""
158
+ # keep these imports here for Windows compatibility
159
+ import grp
160
+ import pwd
161
+
162
+ uid = pwd.getpwnam(user).pw_uid
163
+ gid = grp.getgrnam(user).gr_gid
164
+ os.chown(path, uid, gid)
165
+ for root, dirs, files in os.walk(path):
166
+ for dirname in dirs:
167
+ os.chown(os.path.join(root, dirname), uid, gid)
168
+ for filename in files:
169
+ os.chown(os.path.join(root, filename), uid, gid)
170
+
171
+
172
+ def chmod_r(path: str, mode: int):
173
+ """
174
+ Recursive chmod
175
+ :param path: path to file or directory
176
+ :param mode: permission mask as octal integer value
177
+ """
178
+ if not os.path.exists(path):
179
+ return
180
+ idempotent_chmod(path, mode)
181
+ for root, dirnames, filenames in os.walk(path):
182
+ for dirname in dirnames:
183
+ idempotent_chmod(os.path.join(root, dirname), mode)
184
+ for filename in filenames:
185
+ idempotent_chmod(os.path.join(root, filename), mode)
186
+
187
+
188
+ def idempotent_chmod(path: str, mode: int):
189
+ """
190
+ Perform idempotent chmod on the given file path (non-recursively). The function attempts to call `os.chmod`, and
191
+ will catch and only re-raise exceptions (e.g., PermissionError) if the file does not have the given mode already.
192
+ :param path: path to file
193
+ :param mode: permission mask as octal integer value
194
+ """
195
+ try:
196
+ os.chmod(path, mode)
197
+ except Exception:
198
+ try:
199
+ existing_mode = os.stat(path)
200
+ except FileNotFoundError:
201
+ # file deleted in the meantime, or otherwise not accessible (socket)
202
+ return
203
+ if mode in (existing_mode.st_mode, stat.S_IMODE(existing_mode.st_mode)):
204
+ # file already has the desired permissions -> return
205
+ return
206
+ raise
207
+
208
+
209
+ def rm_rf(path: str):
210
+ """
211
+ Recursively removes a file or directory
212
+ """
213
+ from localstack_cli.utils.platform import is_debian
214
+ from localstack_cli.utils.run import run
215
+
216
+ if not path or not os.path.exists(path):
217
+ return
218
+ # Running the native command can be an order of magnitude faster in Alpine on Travis-CI
219
+ if is_debian():
220
+ try:
221
+ return run(f'rm -rf "{path}"')
222
+ except Exception:
223
+ pass
224
+ # Make sure all files are writeable and dirs executable to remove
225
+ try:
226
+ chmod_r(path, 0o777)
227
+ except PermissionError:
228
+ pass # todo log
229
+ # check if the file is either a normal file, or, e.g., a fifo
230
+ exists_but_non_dir = os.path.exists(path) and not os.path.isdir(path)
231
+ if os.path.isfile(path) or exists_but_non_dir:
232
+ os.remove(path)
233
+ else:
234
+ shutil.rmtree(path)
235
+
236
+
237
+ def cp_r(src: str, dst: str, rm_dest_on_conflict=False, ignore_copystat_errors=False, **kwargs):
238
+ """Recursively copies file/directory"""
239
+ # attention: this patch is not threadsafe
240
+ copystat_orig = shutil.copystat
241
+ if ignore_copystat_errors:
242
+
243
+ def _copystat(*args, **kwargs):
244
+ try:
245
+ return copystat_orig(*args, **kwargs)
246
+ except Exception:
247
+ pass
248
+
249
+ shutil.copystat = _copystat
250
+ try:
251
+ if os.path.isfile(src):
252
+ if os.path.isdir(dst):
253
+ dst = os.path.join(dst, os.path.basename(src))
254
+ return shutil.copyfile(src, dst)
255
+ if "dirs_exist_ok" in inspect.getfullargspec(shutil.copytree).args:
256
+ kwargs["dirs_exist_ok"] = True
257
+ try:
258
+ return shutil.copytree(src, dst, **kwargs)
259
+ except FileExistsError:
260
+ if rm_dest_on_conflict:
261
+ rm_rf(dst)
262
+ return shutil.copytree(src, dst, **kwargs)
263
+ raise
264
+ except Exception as e:
265
+
266
+ def _info(_path):
267
+ return f"{_path} (file={os.path.isfile(_path)}, symlink={os.path.islink(_path)})"
268
+
269
+ LOG.debug("Error copying files from %s to %s: %s", _info(src), _info(dst), e)
270
+ raise
271
+ finally:
272
+ shutil.copystat = copystat_orig
273
+
274
+
275
+ def disk_usage(path: str) -> int:
276
+ """Return the disk usage of the given file or directory."""
277
+
278
+ if not os.path.exists(path):
279
+ return 0
280
+
281
+ if os.path.isfile(path):
282
+ return os.path.getsize(path)
283
+
284
+ total_size = 0
285
+ for dirpath, dirnames, filenames in os.walk(path):
286
+ for f in filenames:
287
+ fp = os.path.join(dirpath, f)
288
+ # skip if it is symbolic link
289
+ if not os.path.islink(fp):
290
+ total_size += os.path.getsize(fp)
291
+ return total_size
292
+
293
+
294
+ def file_exists_not_empty(path: str) -> bool:
295
+ """Return whether the given file or directory exists and is non-empty (i.e., >0 bytes content)"""
296
+ return path and disk_usage(path) > 0
297
+
298
+
299
+ def cleanup_tmp_files():
300
+ for tmp in TMP_FILES:
301
+ try:
302
+ rm_rf(tmp)
303
+ except Exception:
304
+ pass # file likely doesn't exist, or permission denied
305
+ del TMP_FILES[:]
306
+
307
+
308
+ def new_tmp_file(suffix: str | None = None, dir: str | None = None) -> str:
309
+ """Return a path to a new temporary file."""
310
+ tmp_file, tmp_path = tempfile.mkstemp(suffix=suffix, dir=dir)
311
+ os.close(tmp_file)
312
+ TMP_FILES.append(tmp_path)
313
+ return tmp_path
314
+
315
+
316
+ def new_tmp_dir(dir: str | None = None, mode: int = 0o777) -> str:
317
+ """
318
+ Create a new temporary directory with the specified permissions. The directory is added to the tracked temporary
319
+ files.
320
+ :param dir: parent directory for the temporary directory to be created. Systems's default otherwise.
321
+ :param mode: file permission for the directory (default: 0o777)
322
+ :return: the absolute path of the created directory
323
+ """
324
+ folder = tempfile.mkdtemp(dir=dir)
325
+ TMP_FILES.append(folder)
326
+ idempotent_chmod(folder, mode=mode)
327
+ return folder
@@ -0,0 +1,92 @@
1
+ """Higher-order functional tools."""
2
+
3
+ import functools
4
+ import inspect
5
+ import logging
6
+ from collections.abc import Callable
7
+ from typing import Any
8
+
9
+ LOG = logging.getLogger(__name__)
10
+
11
+
12
+ def run_safe(_python_lambda, *args, _default=None, **kwargs):
13
+ print_error = kwargs.get("print_error", False)
14
+ try:
15
+ return _python_lambda(*args, **kwargs)
16
+ except Exception as e:
17
+ if print_error:
18
+ LOG.warning("Unable to execute function: %s", e)
19
+ return _default
20
+
21
+
22
+ def call_safe(
23
+ func: Callable, args: tuple = None, kwargs: dict = None, exception_message: str = None
24
+ ) -> Any | None:
25
+ """
26
+ Call the given function with the given arguments, and if it fails, log the given exception_message.
27
+ If logging.DEBUG is set for the logger, then we also log the traceback.
28
+
29
+ :param func: function to call
30
+ :param args: arguments to pass
31
+ :param kwargs: keyword arguments to pass
32
+ :param exception_message: message to log on exception
33
+ :return: whatever the func returns
34
+ """
35
+ if exception_message is None:
36
+ exception_message = f"error calling function {func.__name__}"
37
+ if args is None:
38
+ args = ()
39
+ if kwargs is None:
40
+ kwargs = {}
41
+
42
+ try:
43
+ return func(*args, **kwargs)
44
+ except Exception as e:
45
+ if LOG.isEnabledFor(logging.DEBUG):
46
+ LOG.exception(exception_message)
47
+ else:
48
+ LOG.warning("%s: %s", exception_message, e)
49
+
50
+
51
+ def prevent_stack_overflow(match_parameters=False):
52
+ """Function decorator to protect a function from stack overflows -
53
+ raises an exception if a (potential) infinite recursion is detected."""
54
+
55
+ def _decorator(wrapped):
56
+ @functools.wraps(wrapped)
57
+ def func(*args, **kwargs):
58
+ def _matches(frame):
59
+ if frame.function != wrapped.__name__:
60
+ return False
61
+ frame = frame.frame
62
+
63
+ if not match_parameters:
64
+ return False
65
+
66
+ # construct dict of arguments this stack frame has been called with
67
+ prev_call_args = {
68
+ frame.f_code.co_varnames[i]: frame.f_locals[frame.f_code.co_varnames[i]]
69
+ for i in range(frame.f_code.co_argcount)
70
+ }
71
+
72
+ # construct dict of arguments the original function has been called with
73
+ sig = inspect.signature(wrapped)
74
+ this_call_args = dict(zip(sig.parameters.keys(), args, strict=False))
75
+ this_call_args.update(kwargs)
76
+
77
+ return prev_call_args == this_call_args
78
+
79
+ matching_frames = [frame[2] for frame in inspect.stack(context=1) if _matches(frame)]
80
+ if matching_frames:
81
+ raise RecursionError("(Potential) infinite recursion detected")
82
+ return wrapped(*args, **kwargs)
83
+
84
+ return func
85
+
86
+ return _decorator
87
+
88
+
89
+ def empty_context_manager():
90
+ import contextlib
91
+
92
+ return contextlib.nullcontext()