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.
- localstack_cli/__init__.py +0 -0
- localstack_cli/cli/__init__.py +10 -0
- localstack_cli/cli/console.py +11 -0
- localstack_cli/cli/core_plugin.py +12 -0
- localstack_cli/cli/exceptions.py +19 -0
- localstack_cli/cli/localstack.py +951 -0
- localstack_cli/cli/lpm.py +138 -0
- localstack_cli/cli/main.py +22 -0
- localstack_cli/cli/plugin.py +39 -0
- localstack_cli/cli/plugins.py +134 -0
- localstack_cli/cli/profiles.py +65 -0
- localstack_cli/config.py +1689 -0
- localstack_cli/constants.py +165 -0
- localstack_cli/logging/__init__.py +0 -0
- localstack_cli/logging/format.py +194 -0
- localstack_cli/logging/setup.py +142 -0
- localstack_cli/packages/__init__.py +25 -0
- localstack_cli/packages/api.py +418 -0
- localstack_cli/packages/core.py +416 -0
- localstack_cli/pro/__init__.py +0 -0
- localstack_cli/pro/core/__init__.py +0 -0
- localstack_cli/pro/core/bootstrap/__init__.py +1 -0
- localstack_cli/pro/core/bootstrap/auth.py +213 -0
- localstack_cli/pro/core/bootstrap/dns_utils.py +55 -0
- localstack_cli/pro/core/bootstrap/entitlements.py +117 -0
- localstack_cli/pro/core/bootstrap/extensions/__init__.py +3 -0
- localstack_cli/pro/core/bootstrap/extensions/__main__.py +106 -0
- localstack_cli/pro/core/bootstrap/extensions/autoinstall.py +63 -0
- localstack_cli/pro/core/bootstrap/extensions/bootstrap.py +97 -0
- localstack_cli/pro/core/bootstrap/extensions/repository.py +374 -0
- localstack_cli/pro/core/bootstrap/licensingv2.py +1259 -0
- localstack_cli/pro/core/bootstrap/pods/__init__.py +0 -0
- localstack_cli/pro/core/bootstrap/pods/api_types.py +17 -0
- localstack_cli/pro/core/bootstrap/pods/constants.py +26 -0
- localstack_cli/pro/core/bootstrap/pods/remotes/__init__.py +0 -0
- localstack_cli/pro/core/bootstrap/pods/remotes/api.py +75 -0
- localstack_cli/pro/core/bootstrap/pods/remotes/configs.py +69 -0
- localstack_cli/pro/core/bootstrap/pods/remotes/params.py +86 -0
- localstack_cli/pro/core/bootstrap/pods_client.py +834 -0
- localstack_cli/pro/core/cli/__init__.py +0 -0
- localstack_cli/pro/core/cli/auth.py +226 -0
- localstack_cli/pro/core/cli/aws.py +16 -0
- localstack_cli/pro/core/cli/cli.py +99 -0
- localstack_cli/pro/core/cli/click_utils.py +21 -0
- localstack_cli/pro/core/cli/cloud_pods.py +465 -0
- localstack_cli/pro/core/cli/diff_view.py +41 -0
- localstack_cli/pro/core/cli/ephemeral.py +199 -0
- localstack_cli/pro/core/cli/extensions.py +492 -0
- localstack_cli/pro/core/cli/iam.py +180 -0
- localstack_cli/pro/core/cli/license.py +90 -0
- localstack_cli/pro/core/cli/localstack.py +118 -0
- localstack_cli/pro/core/cli/replicator.py +378 -0
- localstack_cli/pro/core/cli/state.py +183 -0
- localstack_cli/pro/core/cli/tree_view.py +235 -0
- localstack_cli/pro/core/config.py +556 -0
- localstack_cli/pro/core/constants.py +54 -0
- localstack_cli/pro/core/plugins.py +169 -0
- localstack_cli/runtime/__init__.py +6 -0
- localstack_cli/runtime/exceptions.py +7 -0
- localstack_cli/runtime/hooks.py +73 -0
- localstack_cli/testing/__init__.py +1 -0
- localstack_cli/testing/config.py +4 -0
- localstack_cli/utils/__init__.py +0 -0
- localstack_cli/utils/analytics/__init__.py +12 -0
- localstack_cli/utils/analytics/cli.py +67 -0
- localstack_cli/utils/analytics/client.py +111 -0
- localstack_cli/utils/analytics/events.py +30 -0
- localstack_cli/utils/analytics/logger.py +48 -0
- localstack_cli/utils/analytics/metadata.py +250 -0
- localstack_cli/utils/analytics/publisher.py +160 -0
- localstack_cli/utils/analytics/service_request_aggregator.py +133 -0
- localstack_cli/utils/archives.py +271 -0
- localstack_cli/utils/batching.py +258 -0
- localstack_cli/utils/bootstrap.py +1418 -0
- localstack_cli/utils/checksum.py +313 -0
- localstack_cli/utils/collections.py +554 -0
- localstack_cli/utils/common.py +229 -0
- localstack_cli/utils/container_networking.py +142 -0
- localstack_cli/utils/container_utils/__init__.py +0 -0
- localstack_cli/utils/container_utils/container_client.py +1585 -0
- localstack_cli/utils/container_utils/docker_cmd_client.py +987 -0
- localstack_cli/utils/container_utils/docker_sdk_client.py +1018 -0
- localstack_cli/utils/crypto.py +294 -0
- localstack_cli/utils/docker_utils.py +272 -0
- localstack_cli/utils/files.py +327 -0
- localstack_cli/utils/functions.py +92 -0
- localstack_cli/utils/http.py +326 -0
- localstack_cli/utils/json.py +219 -0
- localstack_cli/utils/net.py +516 -0
- localstack_cli/utils/no_exit_argument_parser.py +19 -0
- localstack_cli/utils/numbers.py +49 -0
- localstack_cli/utils/objects.py +235 -0
- localstack_cli/utils/patch.py +260 -0
- localstack_cli/utils/platform.py +77 -0
- localstack_cli/utils/run.py +514 -0
- localstack_cli/utils/server/__init__.py +0 -0
- localstack_cli/utils/server/tcp_proxy.py +108 -0
- localstack_cli/utils/serving.py +187 -0
- localstack_cli/utils/ssl.py +71 -0
- localstack_cli/utils/strings.py +245 -0
- localstack_cli/utils/sync.py +267 -0
- localstack_cli/utils/threads.py +163 -0
- localstack_cli/utils/time.py +81 -0
- localstack_cli/utils/urls.py +21 -0
- localstack_cli/utils/venv.py +100 -0
- localstack_cli/utils/xml.py +41 -0
- localstack_cli/version.py +34 -0
- playground_ls_cli-4.14.1.dev8.dist-info/METADATA +95 -0
- playground_ls_cli-4.14.1.dev8.dist-info/RECORD +112 -0
- playground_ls_cli-4.14.1.dev8.dist-info/WHEEL +5 -0
- playground_ls_cli-4.14.1.dev8.dist-info/entry_points.txt +17 -0
- playground_ls_cli-4.14.1.dev8.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
import logging
|
|
3
|
+
import os
|
|
4
|
+
import re
|
|
5
|
+
import tempfile
|
|
6
|
+
from abc import ABC, abstractmethod
|
|
7
|
+
|
|
8
|
+
from localstack_cli.utils.files import load_file, rm_rf
|
|
9
|
+
|
|
10
|
+
# Setup logger
|
|
11
|
+
LOG = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ChecksumException(Exception):
|
|
15
|
+
"""Base exception for checksum errors."""
|
|
16
|
+
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ChecksumFormat(ABC):
|
|
21
|
+
"""Abstract base class for checksum format parsers."""
|
|
22
|
+
|
|
23
|
+
@abstractmethod
|
|
24
|
+
def can_parse(self, content: str) -> bool:
|
|
25
|
+
"""
|
|
26
|
+
Check if this parser can handle the given content.
|
|
27
|
+
|
|
28
|
+
:param content: The content to check
|
|
29
|
+
:return: True if parser can handle content, False otherwise
|
|
30
|
+
"""
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
@abstractmethod
|
|
34
|
+
def parse(self, content: str) -> dict[str, str]:
|
|
35
|
+
"""
|
|
36
|
+
Parse the content and return filename to checksum mapping.
|
|
37
|
+
|
|
38
|
+
:param content: The content to parse
|
|
39
|
+
:return: Dictionary mapping filenames to checksums
|
|
40
|
+
"""
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class StandardFormat(ChecksumFormat):
|
|
45
|
+
"""
|
|
46
|
+
Handles standard checksum format.
|
|
47
|
+
|
|
48
|
+
Supports formats like:
|
|
49
|
+
|
|
50
|
+
* ``checksum filename``
|
|
51
|
+
* ``checksum *filename``
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
def can_parse(self, content: str) -> bool:
|
|
55
|
+
lines = content.strip().split("\n")
|
|
56
|
+
for line in lines[:5]: # Check first 5 lines
|
|
57
|
+
if re.match(r"^[a-fA-F0-9]{32,128}\s+\S+", line.strip()):
|
|
58
|
+
return True
|
|
59
|
+
return False
|
|
60
|
+
|
|
61
|
+
def parse(self, content: str) -> dict[str, str]:
|
|
62
|
+
checksums = {}
|
|
63
|
+
for line in content.strip().split("\n"):
|
|
64
|
+
line = line.strip()
|
|
65
|
+
if not line or line.startswith("#"):
|
|
66
|
+
continue
|
|
67
|
+
|
|
68
|
+
# Match: checksum (whitespace) filename
|
|
69
|
+
match = re.match(r"^([a-fA-F0-9]{32,128})\s+(\*?)(.+)$", line)
|
|
70
|
+
if match:
|
|
71
|
+
checksum, star, filename = match.groups()
|
|
72
|
+
checksums[filename.strip()] = checksum.lower()
|
|
73
|
+
|
|
74
|
+
return checksums
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class BSDFormat(ChecksumFormat):
|
|
78
|
+
"""
|
|
79
|
+
Handles BSD-style checksum format.
|
|
80
|
+
|
|
81
|
+
Format: ``SHA512 (filename) = checksum``
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
def can_parse(self, content: str) -> bool:
|
|
85
|
+
lines = content.strip().split("\n")
|
|
86
|
+
for line in lines[:5]:
|
|
87
|
+
if re.match(r"^(MD5|SHA1|SHA256|SHA512)\s*\(.+\)\s*=\s*[a-fA-F0-9]+", line):
|
|
88
|
+
return True
|
|
89
|
+
return False
|
|
90
|
+
|
|
91
|
+
def parse(self, content: str) -> dict[str, str]:
|
|
92
|
+
checksums = {}
|
|
93
|
+
for line in content.strip().split("\n"):
|
|
94
|
+
line = line.strip()
|
|
95
|
+
if not line:
|
|
96
|
+
continue
|
|
97
|
+
|
|
98
|
+
# Match: ALGORITHM (filename) = checksum
|
|
99
|
+
match = re.match(r"^(MD5|SHA1|SHA256|SHA512)\s*\((.+)\)\s*=\s*([a-fA-F0-9]+)$", line)
|
|
100
|
+
if match:
|
|
101
|
+
algo, filename, checksum = match.groups()
|
|
102
|
+
checksums[filename.strip()] = checksum.lower()
|
|
103
|
+
|
|
104
|
+
return checksums
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
class ApacheBSDFormat(ChecksumFormat):
|
|
108
|
+
"""
|
|
109
|
+
Handles Apache's BSD-style format with split checksums.
|
|
110
|
+
|
|
111
|
+
Format::
|
|
112
|
+
|
|
113
|
+
filename: CHECKSUM_PART1
|
|
114
|
+
CHECKSUM_PART2
|
|
115
|
+
CHECKSUM_PART3
|
|
116
|
+
"""
|
|
117
|
+
|
|
118
|
+
def can_parse(self, content: str) -> bool:
|
|
119
|
+
lines = content.strip().split("\n")
|
|
120
|
+
if lines and ":" in lines[0]:
|
|
121
|
+
# Check if it looks like filename: hex_data
|
|
122
|
+
parts = lines[0].split(":", 1)
|
|
123
|
+
if len(parts) == 2 and re.search(r"[a-fA-F0-9\s]+", parts[1]):
|
|
124
|
+
return True
|
|
125
|
+
return False
|
|
126
|
+
|
|
127
|
+
def parse(self, content: str) -> dict[str, str]:
|
|
128
|
+
checksums = {}
|
|
129
|
+
lines = content.strip().split("\n")
|
|
130
|
+
|
|
131
|
+
current_file = None
|
|
132
|
+
checksum_parts = []
|
|
133
|
+
|
|
134
|
+
for line in lines:
|
|
135
|
+
if ":" in line and not line.startswith(" "):
|
|
136
|
+
# New file entry
|
|
137
|
+
if current_file and checksum_parts:
|
|
138
|
+
# Save previous file's checksum
|
|
139
|
+
full_checksum = "".join(checksum_parts).replace(" ", "").lower()
|
|
140
|
+
if re.match(r"^[a-fA-F0-9]+$", full_checksum):
|
|
141
|
+
checksums[current_file] = full_checksum
|
|
142
|
+
|
|
143
|
+
# Start new file
|
|
144
|
+
parts = line.split(":", 1)
|
|
145
|
+
current_file = parts[0].strip()
|
|
146
|
+
checksum_part = parts[1].strip()
|
|
147
|
+
checksum_parts = [checksum_part]
|
|
148
|
+
elif line.strip() and current_file:
|
|
149
|
+
# Continuation of checksum
|
|
150
|
+
checksum_parts.append(line.strip())
|
|
151
|
+
|
|
152
|
+
# Don't forget the last file
|
|
153
|
+
if current_file and checksum_parts:
|
|
154
|
+
full_checksum = "".join(checksum_parts).replace(" ", "").lower()
|
|
155
|
+
if re.match(r"^[a-fA-F0-9]+$", full_checksum):
|
|
156
|
+
checksums[current_file] = full_checksum
|
|
157
|
+
|
|
158
|
+
return checksums
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
class ChecksumParser:
|
|
162
|
+
"""Main parser that tries different checksum formats."""
|
|
163
|
+
|
|
164
|
+
def __init__(self):
|
|
165
|
+
"""Initialize parser with available format parsers."""
|
|
166
|
+
self.formats = [
|
|
167
|
+
StandardFormat(),
|
|
168
|
+
BSDFormat(),
|
|
169
|
+
ApacheBSDFormat(),
|
|
170
|
+
]
|
|
171
|
+
|
|
172
|
+
def parse(self, content: str) -> dict[str, str]:
|
|
173
|
+
"""
|
|
174
|
+
Try each format parser until one works.
|
|
175
|
+
|
|
176
|
+
:param content: The content to parse
|
|
177
|
+
:return: Dictionary mapping filenames to checksums
|
|
178
|
+
"""
|
|
179
|
+
for format_parser in self.formats:
|
|
180
|
+
if format_parser.can_parse(content):
|
|
181
|
+
result = format_parser.parse(content)
|
|
182
|
+
if result:
|
|
183
|
+
return result
|
|
184
|
+
|
|
185
|
+
return {}
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def parse_checksum_file_from_url(checksum_url: str) -> dict[str, str]:
|
|
189
|
+
"""
|
|
190
|
+
Parse a SHA checksum file from a URL using multiple format parsers.
|
|
191
|
+
|
|
192
|
+
:param checksum_url: URL of the checksum file
|
|
193
|
+
:return: Dictionary mapping filenames to checksums
|
|
194
|
+
"""
|
|
195
|
+
# import here to avoid circular dependency issues
|
|
196
|
+
from localstack.utils.http import download
|
|
197
|
+
|
|
198
|
+
checksum_name = os.path.basename(checksum_url)
|
|
199
|
+
checksum_path = os.path.join(tempfile.gettempdir(), checksum_name)
|
|
200
|
+
try:
|
|
201
|
+
download(checksum_url, checksum_path)
|
|
202
|
+
checksum_content = load_file(checksum_path)
|
|
203
|
+
|
|
204
|
+
parser = ChecksumParser()
|
|
205
|
+
checksums = parser.parse(checksum_content)
|
|
206
|
+
|
|
207
|
+
return checksums
|
|
208
|
+
finally:
|
|
209
|
+
rm_rf(checksum_path)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def calculate_file_checksum(file_path: str, algorithm: str = "sha256") -> str:
|
|
213
|
+
"""
|
|
214
|
+
Calculate checksum of a local file.
|
|
215
|
+
|
|
216
|
+
:param file_path: Path to the file
|
|
217
|
+
:param algorithm: Hash algorithm to use
|
|
218
|
+
:return: Calculated checksum as hexadecimal string
|
|
219
|
+
|
|
220
|
+
note: Supported algorithms: 'md5', 'sha1', 'sha256', 'sha512'
|
|
221
|
+
"""
|
|
222
|
+
hash_func = getattr(hashlib, algorithm)()
|
|
223
|
+
|
|
224
|
+
with open(file_path, "rb") as f:
|
|
225
|
+
# Read file in chunks to handle large files efficiently
|
|
226
|
+
for chunk in iter(lambda: f.read(8192), b""):
|
|
227
|
+
hash_func.update(chunk)
|
|
228
|
+
|
|
229
|
+
return hash_func.hexdigest()
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def verify_local_file_with_checksum_url(file_path: str, checksum_url: str, filename=None) -> bool:
|
|
233
|
+
"""
|
|
234
|
+
Verify a local file against checksums from an online checksum file.
|
|
235
|
+
|
|
236
|
+
:param file_path: Path to the local file to verify
|
|
237
|
+
:param checksum_url: URL of the checksum file
|
|
238
|
+
:param filename: Filename to look for in checksum file (defaults to basename of file_path)
|
|
239
|
+
:return: True if verification succeeds, False otherwise
|
|
240
|
+
|
|
241
|
+
note: The algorithm is automatically detected based on checksum length:
|
|
242
|
+
|
|
243
|
+
* 32 characters: MD5
|
|
244
|
+
* 40 characters: SHA1
|
|
245
|
+
* 64 characters: SHA256
|
|
246
|
+
* 128 characters: SHA512
|
|
247
|
+
"""
|
|
248
|
+
# Get checksums from URL
|
|
249
|
+
LOG.debug("Fetching checksums from %s...", checksum_url)
|
|
250
|
+
checksums = parse_checksum_file_from_url(checksum_url)
|
|
251
|
+
|
|
252
|
+
if not checksums:
|
|
253
|
+
raise ChecksumException(f"No checksums found in {checksum_url}")
|
|
254
|
+
|
|
255
|
+
# Determine filename to look for
|
|
256
|
+
if filename is None:
|
|
257
|
+
filename = os.path.basename(file_path)
|
|
258
|
+
|
|
259
|
+
# Find checksum for our file
|
|
260
|
+
if filename not in checksums:
|
|
261
|
+
# Try with different path variations
|
|
262
|
+
possible_names = [
|
|
263
|
+
filename,
|
|
264
|
+
os.path.basename(filename), # just filename without path
|
|
265
|
+
filename.replace("\\", "/"), # Unix-style paths
|
|
266
|
+
filename.replace("/", "\\"), # Windows-style paths
|
|
267
|
+
]
|
|
268
|
+
|
|
269
|
+
found = False
|
|
270
|
+
for name in possible_names:
|
|
271
|
+
if name in checksums:
|
|
272
|
+
filename = name
|
|
273
|
+
found = True
|
|
274
|
+
break
|
|
275
|
+
|
|
276
|
+
if not found:
|
|
277
|
+
raise ChecksumException(f"Checksum for {filename} not found in {checksum_url}")
|
|
278
|
+
|
|
279
|
+
expected_checksum = checksums[filename]
|
|
280
|
+
|
|
281
|
+
# Detect algorithm based on checksum length
|
|
282
|
+
checksum_length = len(expected_checksum)
|
|
283
|
+
if checksum_length == 32:
|
|
284
|
+
algorithm = "md5"
|
|
285
|
+
elif checksum_length == 40:
|
|
286
|
+
algorithm = "sha1"
|
|
287
|
+
elif checksum_length == 64:
|
|
288
|
+
algorithm = "sha256"
|
|
289
|
+
elif checksum_length == 128:
|
|
290
|
+
algorithm = "sha512"
|
|
291
|
+
else:
|
|
292
|
+
raise ChecksumException(f"Unsupported checksum length: {checksum_length}")
|
|
293
|
+
|
|
294
|
+
# Calculate checksum of local file
|
|
295
|
+
LOG.debug("Calculating %s checksum of %s...", algorithm, file_path)
|
|
296
|
+
calculated_checksum = calculate_file_checksum(file_path, algorithm)
|
|
297
|
+
|
|
298
|
+
is_valid = calculated_checksum == expected_checksum.lower()
|
|
299
|
+
|
|
300
|
+
if not is_valid:
|
|
301
|
+
LOG.error(
|
|
302
|
+
"Checksum mismatch for %s: calculated %s, expected %s",
|
|
303
|
+
file_path,
|
|
304
|
+
calculated_checksum,
|
|
305
|
+
expected_checksum,
|
|
306
|
+
)
|
|
307
|
+
raise ChecksumException(
|
|
308
|
+
f"Checksum mismatch for {file_path}: calculated {calculated_checksum}, expected {expected_checksum}"
|
|
309
|
+
)
|
|
310
|
+
LOG.debug("Checksum verification successful for %s", file_path)
|
|
311
|
+
|
|
312
|
+
# Compare checksums
|
|
313
|
+
return calculated_checksum == expected_checksum.lower()
|