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,1689 @@
1
+ import ipaddress
2
+ import logging
3
+ import os
4
+ import platform
5
+ import re
6
+ import socket
7
+ import subprocess
8
+ import tempfile
9
+ import time
10
+ import warnings
11
+ from collections import defaultdict
12
+ from collections.abc import Mapping
13
+ from typing import Any, TypeVar
14
+
15
+ from localstack_cli import constants
16
+ from localstack_cli.constants import (
17
+ DEFAULT_BUCKET_MARKER_LOCAL,
18
+ DEFAULT_DEVELOP_PORT,
19
+ DEFAULT_VOLUME_DIR,
20
+ ENV_INTERNAL_TEST_COLLECT_METRIC,
21
+ ENV_INTERNAL_TEST_RUN,
22
+ ENV_INTERNAL_TEST_STORE_METRICS_IN_LOCALSTACK,
23
+ FALSE_STRINGS,
24
+ LOCALHOST,
25
+ LOCALHOST_IP,
26
+ LOCALSTACK_ROOT_FOLDER,
27
+ LOG_LEVELS,
28
+ TRACE_LOG_LEVELS,
29
+ TRUE_STRINGS,
30
+ )
31
+
32
+ T = TypeVar("T", str, int)
33
+
34
+ # keep track of start time, for performance debugging
35
+ load_start_time = time.time()
36
+
37
+
38
+ class Directories:
39
+ """
40
+ Holds different directories available to localstack. Some directories are shared between the host and the
41
+ localstack container, some live only on the host and others in the container.
42
+
43
+ Attributes:
44
+ static_libs: container only; binaries and libraries statically packaged with the image
45
+ var_libs: shared; binaries and libraries+data computed at runtime: lazy-loaded binaries, ssl cert, ...
46
+ cache: shared; ephemeral data that has to persist across localstack runs and reboots
47
+ tmp: container only; ephemeral data that has to persist across localstack runs but not reboots
48
+ mounted_tmp: shared; same as above, but shared for persistence across different containers, tests, ...
49
+ functions: shared; volume to communicate between host<->lambda containers
50
+ data: shared; holds localstack state, pods, ...
51
+ config: host only; pre-defined configuration values, cached credentials, machine id, ...
52
+ init: shared; user-defined provisioning scripts executed in the container when it starts
53
+ logs: shared; log files produced by localstack
54
+ """
55
+
56
+ static_libs: str
57
+ var_libs: str
58
+ cache: str
59
+ tmp: str
60
+ mounted_tmp: str
61
+ functions: str
62
+ data: str
63
+ config: str
64
+ init: str
65
+ logs: str
66
+
67
+ def __init__(
68
+ self,
69
+ static_libs: str,
70
+ var_libs: str,
71
+ cache: str,
72
+ tmp: str,
73
+ mounted_tmp: str,
74
+ functions: str,
75
+ data: str,
76
+ config: str,
77
+ init: str,
78
+ logs: str,
79
+ ) -> None:
80
+ super().__init__()
81
+ self.static_libs = static_libs
82
+ self.var_libs = var_libs
83
+ self.cache = cache
84
+ self.tmp = tmp
85
+ self.mounted_tmp = mounted_tmp
86
+ self.functions = functions
87
+ self.data = data
88
+ self.config = config
89
+ self.init = init
90
+ self.logs = logs
91
+
92
+ @staticmethod
93
+ def defaults() -> "Directories":
94
+ """Returns Localstack directory paths based on the localstack filesystem hierarchy."""
95
+ return Directories(
96
+ static_libs="/usr/lib/localstack",
97
+ var_libs=f"{DEFAULT_VOLUME_DIR}/lib",
98
+ cache=f"{DEFAULT_VOLUME_DIR}/cache",
99
+ tmp=os.path.join(tempfile.gettempdir(), "localstack"),
100
+ mounted_tmp=f"{DEFAULT_VOLUME_DIR}/tmp",
101
+ functions=f"{DEFAULT_VOLUME_DIR}/tmp", # FIXME: remove - this was misconceived
102
+ data=f"{DEFAULT_VOLUME_DIR}/state",
103
+ logs=f"{DEFAULT_VOLUME_DIR}/logs",
104
+ config="/etc/localstack/conf.d", # for future use
105
+ init="/etc/localstack/init",
106
+ )
107
+
108
+ @staticmethod
109
+ def for_container() -> "Directories":
110
+ """
111
+ Returns Localstack directory paths as they are defined within the container. Everything shared and writable
112
+ lives in /var/lib/localstack or {tempfile.gettempdir()}/localstack.
113
+
114
+ :returns: Directories object
115
+ """
116
+ defaults = Directories.defaults()
117
+
118
+ return Directories(
119
+ static_libs=defaults.static_libs,
120
+ var_libs=defaults.var_libs,
121
+ cache=defaults.cache,
122
+ tmp=defaults.tmp,
123
+ mounted_tmp=defaults.mounted_tmp,
124
+ functions=defaults.functions,
125
+ data=defaults.data if PERSISTENCE else os.path.join(defaults.tmp, "state"),
126
+ config=defaults.config,
127
+ logs=defaults.logs,
128
+ init=defaults.init,
129
+ )
130
+
131
+ @staticmethod
132
+ def for_host() -> "Directories":
133
+ """Return directories used for running localstack in host mode. Note that these are *not* the directories
134
+ that are mounted into the container when the user starts localstack."""
135
+ root = os.environ.get("FILESYSTEM_ROOT") or os.path.join(
136
+ LOCALSTACK_ROOT_FOLDER, ".filesystem"
137
+ )
138
+ root = os.path.abspath(root)
139
+
140
+ defaults = Directories.for_container()
141
+
142
+ tmp = os.path.join(root, defaults.tmp.lstrip("/"))
143
+ data = os.path.join(root, defaults.data.lstrip("/"))
144
+
145
+ return Directories(
146
+ static_libs=os.path.join(root, defaults.static_libs.lstrip("/")),
147
+ var_libs=os.path.join(root, defaults.var_libs.lstrip("/")),
148
+ cache=os.path.join(root, defaults.cache.lstrip("/")),
149
+ tmp=tmp,
150
+ mounted_tmp=os.path.join(root, defaults.mounted_tmp.lstrip("/")),
151
+ functions=os.path.join(root, defaults.functions.lstrip("/")),
152
+ data=data if PERSISTENCE else os.path.join(tmp, "state"),
153
+ config=os.path.join(root, defaults.config.lstrip("/")),
154
+ init=os.path.join(root, defaults.init.lstrip("/")),
155
+ logs=os.path.join(root, defaults.logs.lstrip("/")),
156
+ )
157
+
158
+ @staticmethod
159
+ def for_cli() -> "Directories":
160
+ """Returns directories used for when running localstack CLI commands from the host system. Unlike
161
+ ``for_container``, these needs to be cross-platform. Ideally, this should not be needed at all,
162
+ because the localstack runtime and CLI do not share any control paths. There are a handful of
163
+ situations where directories or files may be created lazily for CLI commands. Some paths are
164
+ intentionally set to None to provoke errors if these paths are used from the CLI - which they
165
+ shouldn't. This is a symptom of not having a clear separation between CLI/runtime code, which will
166
+ be a future project."""
167
+ import tempfile
168
+
169
+ from localstack_cli.utils import files
170
+
171
+ tmp_dir = os.path.join(tempfile.gettempdir(), "localstack-cli")
172
+ cache_dir = (files.get_user_cache_dir()).absolute() / "localstack-cli"
173
+
174
+ return Directories(
175
+ static_libs=None,
176
+ var_libs=None,
177
+ cache=str(cache_dir), # used by analytics metadata
178
+ tmp=tmp_dir,
179
+ mounted_tmp=tmp_dir,
180
+ functions=None,
181
+ data=os.path.join(tmp_dir, "state"), # used by localstack-pro config TODO: remove
182
+ logs=os.path.join(tmp_dir, "logs"), # used for container logs
183
+ config=None, # in the context of the CLI, config.CONFIG_DIR should be used
184
+ init=None,
185
+ )
186
+
187
+ def mkdirs(self):
188
+ for folder in [
189
+ self.static_libs,
190
+ self.var_libs,
191
+ self.cache,
192
+ self.tmp,
193
+ self.mounted_tmp,
194
+ self.functions,
195
+ self.data,
196
+ self.config,
197
+ self.init,
198
+ self.logs,
199
+ ]:
200
+ if folder and not os.path.exists(folder):
201
+ try:
202
+ os.makedirs(folder)
203
+ except Exception:
204
+ # this can happen due to a race condition when starting
205
+ # multiple processes in parallel. Should be safe to ignore
206
+ pass
207
+
208
+ def __str__(self):
209
+ return str(self.__dict__)
210
+
211
+
212
+ def eval_log_type(env_var_name: str) -> str | bool:
213
+ """Get the log type from environment variable"""
214
+ ls_log = os.environ.get(env_var_name, "").lower().strip()
215
+ return ls_log if ls_log in LOG_LEVELS else False
216
+
217
+
218
+ def parse_boolean_env(env_var_name: str) -> bool | None:
219
+ """Parse the value of the given env variable and return True/False, or None if it is not a boolean value."""
220
+ value = os.environ.get(env_var_name, "").lower().strip()
221
+ if value in TRUE_STRINGS:
222
+ return True
223
+ if value in FALSE_STRINGS:
224
+ return False
225
+ return None
226
+
227
+
228
+ def parse_comma_separated_list(env_var_name: str) -> list[str]:
229
+ """Parse a comma separated list from the given environment variable."""
230
+ return os.environ.get(env_var_name, "").strip().split(",")
231
+
232
+
233
+ def is_env_true(env_var_name: str) -> bool:
234
+ """Whether the given environment variable has a truthy value."""
235
+ return os.environ.get(env_var_name, "").lower().strip() in TRUE_STRINGS
236
+
237
+
238
+ def is_env_not_false(env_var_name: str) -> bool:
239
+ """Whether the given environment variable is empty or has a truthy value."""
240
+ return os.environ.get(env_var_name, "").lower().strip() not in FALSE_STRINGS
241
+
242
+
243
+ def load_environment(profiles: str = None, env=os.environ) -> list[str]:
244
+ """Loads the environment variables from ~/.localstack/{profile}.env, for each profile listed in the profiles.
245
+ :param env: environment to load profile to. Defaults to `os.environ`
246
+ :param profiles: a comma separated list of profiles to load (defaults to "default")
247
+ :returns str: the list of the actually loaded profiles (might be the fallback)
248
+ """
249
+ if not profiles:
250
+ profiles = "default"
251
+
252
+ profiles = profiles.split(",")
253
+ environment = {}
254
+ import dotenv
255
+
256
+ for profile in profiles:
257
+ profile = profile.strip()
258
+ path = os.path.join(CONFIG_DIR, f"{profile}.env")
259
+ if not os.path.exists(path):
260
+ continue
261
+ environment.update(dotenv.dotenv_values(path))
262
+
263
+ for k, v in environment.items():
264
+ # we do not want to override the environment
265
+ if k not in env and v is not None:
266
+ env[k] = v
267
+
268
+ return profiles
269
+
270
+
271
+ def is_persistence_enabled() -> bool:
272
+ return PERSISTENCE and dirs.data
273
+
274
+
275
+ def is_linux() -> bool:
276
+ return platform.system() == "Linux"
277
+
278
+
279
+ def is_macos() -> bool:
280
+ return platform.system() == "Darwin"
281
+
282
+
283
+ def is_windows() -> bool:
284
+ return platform.system().lower() == "windows"
285
+
286
+
287
+ def is_wsl() -> bool:
288
+ return platform.system().lower() == "linux" and os.environ.get("WSL_DISTRO_NAME") is not None
289
+
290
+
291
+ def ping(host):
292
+ """Returns True if the host responds to a ping request"""
293
+ is_in_windows = is_windows()
294
+ ping_opts = "-n 1 -w 2000" if is_in_windows else "-c 1 -W 2"
295
+ args = f"ping {ping_opts} {host}"
296
+ return (
297
+ subprocess.call(
298
+ args, shell=not is_in_windows, stdout=subprocess.PIPE, stderr=subprocess.PIPE
299
+ )
300
+ == 0
301
+ )
302
+
303
+
304
+ def in_docker():
305
+ """
306
+ Returns True if running in a docker container, else False
307
+ Ref. https://docs.docker.com/config/containers/runmetrics/#control-groups
308
+ """
309
+ if OVERRIDE_IN_DOCKER is not None:
310
+ return OVERRIDE_IN_DOCKER
311
+
312
+ # check some marker files that we create in our Dockerfiles
313
+ for path in [
314
+ "/usr/lib/localstack/.community-version",
315
+ "/usr/lib/localstack/.pro-version",
316
+ "/tmp/localstack/.marker",
317
+ ]:
318
+ if os.path.isfile(path):
319
+ return True
320
+
321
+ # details: https://github.com/localstack/localstack/pull/4352
322
+ if os.path.exists("/.dockerenv"):
323
+ return True
324
+ if os.path.exists("/run/.containerenv"):
325
+ return True
326
+
327
+ if not os.path.exists("/proc/1/cgroup"):
328
+ return False
329
+ try:
330
+ if any(
331
+ [
332
+ os.path.exists("/sys/fs/cgroup/memory/docker/"),
333
+ any(
334
+ "docker-" in file_names
335
+ for file_names in os.listdir("/sys/fs/cgroup/memory/system.slice")
336
+ ),
337
+ os.path.exists("/sys/fs/cgroup/docker/"),
338
+ any(
339
+ "docker-" in file_names
340
+ for file_names in os.listdir("/sys/fs/cgroup/system.slice/")
341
+ ),
342
+ ]
343
+ ):
344
+ return False
345
+ except Exception:
346
+ pass
347
+ with open("/proc/1/cgroup") as ifh:
348
+ content = ifh.read()
349
+ if "docker" in content or "buildkit" in content:
350
+ return True
351
+ os_hostname = socket.gethostname()
352
+ if os_hostname and os_hostname in content:
353
+ return True
354
+
355
+ # containerd does not set any specific file or config, but it does use
356
+ # io.containerd.snapshotter.v1.overlayfs as the overlay filesystem for `/`.
357
+ try:
358
+ with open("/proc/mounts") as infile:
359
+ for line in infile:
360
+ line = line.strip()
361
+
362
+ if not line:
363
+ continue
364
+
365
+ # skip comments
366
+ if line[0] == "#":
367
+ continue
368
+
369
+ # format (man 5 fstab)
370
+ # <spec> <mount point> <type> <options> <rest>...
371
+ parts = line.split()
372
+ if len(parts) < 4:
373
+ # badly formatted line
374
+ continue
375
+
376
+ mount_point = parts[1]
377
+ options = parts[3]
378
+
379
+ # only consider the root filesystem
380
+ if mount_point != "/":
381
+ continue
382
+
383
+ if "io.containerd" in options:
384
+ return True
385
+
386
+ except FileNotFoundError:
387
+ pass
388
+
389
+ return False
390
+
391
+
392
+ # whether the `in_docker` check should always return True or False
393
+ OVERRIDE_IN_DOCKER = parse_boolean_env("OVERRIDE_IN_DOCKER")
394
+
395
+ is_in_docker = in_docker()
396
+ is_in_linux = is_linux()
397
+ is_in_macos = is_macos()
398
+ is_in_windows = is_windows()
399
+ is_in_wsl = is_wsl()
400
+ default_ip = "0.0.0.0" if is_in_docker else "127.0.0.1"
401
+
402
+ # CLI specific: the configuration profile to load
403
+ CONFIG_PROFILE = os.environ.get("CONFIG_PROFILE", "").strip()
404
+
405
+ # CLI specific: host configuration directory
406
+ CONFIG_DIR = os.environ.get("CONFIG_DIR", os.path.expanduser("~/.localstack"))
407
+
408
+ # keep this on top to populate the environment
409
+ try:
410
+ # CLI specific: the actually loaded configuration profile
411
+ LOADED_PROFILES = load_environment(CONFIG_PROFILE)
412
+ except ImportError:
413
+ # dotenv may not be available in lambdas or other environments where config is loaded
414
+ LOADED_PROFILES = None
415
+
416
+ # loaded components name - default: all components are loaded and the first one is chosen
417
+ RUNTIME_COMPONENTS = os.environ.get("RUNTIME_COMPONENTS", "").strip()
418
+
419
+ # directory for persisting data (TODO: deprecated, simply use PERSISTENCE=1)
420
+ DATA_DIR = os.environ.get("DATA_DIR", "").strip()
421
+
422
+ # whether localstack should persist service state across localstack runs
423
+ PERSISTENCE = is_env_true("PERSISTENCE")
424
+
425
+ # the strategy for loading snapshots from disk when `PERSISTENCE=1` is used (on_startup, on_request, manual)
426
+ SNAPSHOT_LOAD_STRATEGY = os.environ.get("SNAPSHOT_LOAD_STRATEGY", "").upper()
427
+
428
+ # the strategy saving snapshots to disk when `PERSISTENCE=1` is used (on_shutdown, on_request, scheduled, manual)
429
+ SNAPSHOT_SAVE_STRATEGY = os.environ.get("SNAPSHOT_SAVE_STRATEGY", "").upper()
430
+
431
+ # the flush interval (in seconds) for persistence when the snapshot save strategy is set to "scheduled"
432
+ SNAPSHOT_FLUSH_INTERVAL = int(os.environ.get("SNAPSHOT_FLUSH_INTERVAL") or 15)
433
+
434
+ # whether to clear config.dirs.tmp on startup and shutdown
435
+ CLEAR_TMP_FOLDER = is_env_not_false("CLEAR_TMP_FOLDER")
436
+
437
+ # folder for temporary files and data
438
+ TMP_FOLDER = os.path.join(tempfile.gettempdir(), "localstack")
439
+
440
+ # this is exclusively for the CLI to configure the container mount into /var/lib/localstack
441
+ VOLUME_DIR = os.environ.get("LOCALSTACK_VOLUME_DIR", "").strip() or TMP_FOLDER
442
+
443
+ # fix for Mac OS, to be able to mount /var/folders in Docker
444
+ if TMP_FOLDER.startswith("/var/folders/") and os.path.exists(f"/private{TMP_FOLDER}"):
445
+ TMP_FOLDER = f"/private{TMP_FOLDER}"
446
+
447
+ # whether to enable verbose debug logging ("LOG" is used when using the CLI with LOCALSTACK_LOG instead of LS_LOG)
448
+ LS_LOG = eval_log_type("LS_LOG") or eval_log_type("LOG")
449
+ DEBUG = is_env_true("DEBUG") or LS_LOG in TRACE_LOG_LEVELS
450
+
451
+ # PUBLIC PREVIEW: 0 (default), 1 (preview)
452
+ # When enabled it triggers specialised workflows for the debugging.
453
+ LAMBDA_DEBUG_MODE = is_env_true("LAMBDA_DEBUG_MODE")
454
+
455
+ # path to the lambda debug mode configuration file.
456
+ LAMBDA_DEBUG_MODE_CONFIG_PATH = os.environ.get("LAMBDA_DEBUG_MODE_CONFIG_PATH")
457
+
458
+ # EXPERIMENTAL: allow setting custom log levels for individual loggers
459
+ LOG_LEVEL_OVERRIDES = os.environ.get("LOG_LEVEL_OVERRIDES", "")
460
+
461
+ # whether to enable debugpy
462
+ DEVELOP = is_env_true("DEVELOP")
463
+
464
+ # PORT FOR DEBUGGER
465
+ DEVELOP_PORT = int(os.environ.get("DEVELOP_PORT", "").strip() or DEFAULT_DEVELOP_PORT)
466
+
467
+ # whether to make debugpy wait for a debbuger client
468
+ WAIT_FOR_DEBUGGER = is_env_true("WAIT_FOR_DEBUGGER")
469
+
470
+ # whether to assume http or https for `get_protocol`
471
+ USE_SSL = is_env_true("USE_SSL")
472
+
473
+ # Whether to report internal failures as 500 or 501 errors.
474
+ FAIL_FAST = is_env_true("FAIL_FAST")
475
+
476
+ # whether to run in TF compatibility mode for TF integration tests
477
+ # (e.g., returning verbatim ports for ELB resources, rather than edge port 4566, etc.)
478
+ TF_COMPAT_MODE = is_env_true("TF_COMPAT_MODE")
479
+
480
+ # default encoding used to convert strings to byte arrays (mainly for Python 3 compatibility)
481
+ DEFAULT_ENCODING = "utf-8"
482
+
483
+ # path to local Docker UNIX domain socket
484
+ DOCKER_SOCK = os.environ.get("DOCKER_SOCK", "").strip() or "/var/run/docker.sock"
485
+
486
+ # additional flags to pass to "docker run" when starting the stack in Docker
487
+ DOCKER_FLAGS = os.environ.get("DOCKER_FLAGS", "").strip()
488
+
489
+ # command used to run Docker containers (e.g., set to "sudo docker" to run as sudo)
490
+ DOCKER_CMD = os.environ.get("DOCKER_CMD", "").strip() or "docker"
491
+
492
+ # use the command line docker client instead of the new sdk version, might get removed in the future
493
+ LEGACY_DOCKER_CLIENT = is_env_true("LEGACY_DOCKER_CLIENT")
494
+
495
+ # Docker image to use when starting up containers for port checks
496
+ PORTS_CHECK_DOCKER_IMAGE = os.environ.get("PORTS_CHECK_DOCKER_IMAGE", "").strip()
497
+
498
+ # global prefix to prepend to Docker image names (e.g., for using a custom registry mirror)
499
+ DOCKER_GLOBAL_IMAGE_PREFIX = os.environ.get("DOCKER_GLOBAL_IMAGE_PREFIX", "").strip()
500
+
501
+
502
+ def is_trace_logging_enabled():
503
+ if LS_LOG:
504
+ log_level = str(LS_LOG).upper()
505
+ return log_level.lower() in TRACE_LOG_LEVELS
506
+ return False
507
+
508
+
509
+ # set log levels immediately, but will be overwritten later by setup_logging
510
+ if DEBUG:
511
+ logging.getLogger("").setLevel(logging.DEBUG)
512
+ logging.getLogger("localstack").setLevel(logging.DEBUG)
513
+
514
+ LOG = logging.getLogger(__name__)
515
+ if is_trace_logging_enabled():
516
+ load_end_time = time.time()
517
+ LOG.debug(
518
+ "Initializing the configuration took %s ms", int((load_end_time - load_start_time) * 1000)
519
+ )
520
+
521
+
522
+ def is_ipv6_address(host: str) -> bool:
523
+ """
524
+ Returns True if the given host is an IPv6 address.
525
+ """
526
+
527
+ if not host:
528
+ return False
529
+
530
+ try:
531
+ ipaddress.IPv6Address(host)
532
+ return True
533
+ except ipaddress.AddressValueError:
534
+ return False
535
+
536
+
537
+ class HostAndPort:
538
+ """
539
+ Definition of an address for a server to listen to.
540
+
541
+ Includes a `parse` method to convert from `str`, allowing for default fallbacks, as well as
542
+ some helper methods to help tests - particularly testing for equality and a hash function
543
+ so that `HostAndPort` instances can be used as keys to dictionaries.
544
+ """
545
+
546
+ host: str
547
+ port: int
548
+
549
+ def __init__(self, host: str, port: int):
550
+ self.host = host
551
+ self.port = port
552
+
553
+ @classmethod
554
+ def parse(
555
+ cls,
556
+ input: str,
557
+ default_host: str,
558
+ default_port: int,
559
+ ) -> "HostAndPort":
560
+ """
561
+ Parse a `HostAndPort` from strings like:
562
+ - 0.0.0.0:4566 -> host=0.0.0.0, port=4566
563
+ - 0.0.0.0 -> host=0.0.0.0, port=`default_port`
564
+ - :4566 -> host=`default_host`, port=4566
565
+ - [::]:4566 -> host=[::], port=4566
566
+ - [::1] -> host=[::1], port=`default_port`
567
+ """
568
+ host, port = default_host, default_port
569
+
570
+ # recognize IPv6 addresses (+ port)
571
+ if input.startswith("["):
572
+ ipv6_pattern = re.compile(r"^\[(?P<host>[^]]+)\](:(?P<port>\d+))?$")
573
+ match = ipv6_pattern.match(input)
574
+
575
+ if match:
576
+ host = match.group("host")
577
+ if not is_ipv6_address(host):
578
+ raise ValueError(
579
+ f"input looks like an IPv6 address (is enclosed in square brackets), but is not valid: {host}"
580
+ )
581
+ port_s = match.group("port")
582
+ if port_s:
583
+ port = cls._validate_port(port_s)
584
+ else:
585
+ raise ValueError(
586
+ f'input looks like an IPv6 address, but is invalid. Should be formatted "[ip]:port": {input}'
587
+ )
588
+
589
+ # recognize IPv4 address + port
590
+ elif ":" in input:
591
+ hostname, port_s = input.split(":", 1)
592
+ if hostname.strip():
593
+ host = hostname.strip()
594
+ port = cls._validate_port(port_s)
595
+ else:
596
+ if input.strip():
597
+ host = input.strip()
598
+
599
+ # validation
600
+ if port < 0 or port >= 2**16:
601
+ raise ValueError("port out of range")
602
+
603
+ return cls(host=host, port=port)
604
+
605
+ @classmethod
606
+ def _validate_port(cls, port_s: str) -> int:
607
+ try:
608
+ port = int(port_s)
609
+ except ValueError as e:
610
+ raise ValueError(f"specified port {port_s} not a number") from e
611
+
612
+ return port
613
+
614
+ def _get_unprivileged_port_range_start(self) -> int:
615
+ try:
616
+ with open("/proc/sys/net/ipv4/ip_unprivileged_port_start") as unprivileged_port_start:
617
+ port = unprivileged_port_start.read()
618
+ return int(port.strip())
619
+ except Exception:
620
+ return 1024
621
+
622
+ def is_unprivileged(self) -> bool:
623
+ return self.port >= self._get_unprivileged_port_range_start()
624
+
625
+ def host_and_port(self) -> str:
626
+ formatted_host = f"[{self.host}]" if is_ipv6_address(self.host) else self.host
627
+ return f"{formatted_host}:{self.port}" if self.port is not None else formatted_host
628
+
629
+ def __hash__(self) -> int:
630
+ return hash((self.host, self.port))
631
+
632
+ # easier tests
633
+ def __eq__(self, other: "str | HostAndPort") -> bool:
634
+ if isinstance(other, self.__class__):
635
+ return self.host == other.host and self.port == other.port
636
+ elif isinstance(other, str):
637
+ return str(self) == other
638
+ else:
639
+ raise TypeError(f"cannot compare {self.__class__} to {other.__class__}")
640
+
641
+ def __str__(self) -> str:
642
+ return self.host_and_port()
643
+
644
+ def __repr__(self) -> str:
645
+ return f"HostAndPort(host={self.host}, port={self.port})"
646
+
647
+
648
+ class UniqueHostAndPortList(list[HostAndPort]):
649
+ """
650
+ Container type that ensures that ports added to the list are unique based
651
+ on these rules:
652
+ - :: "trumps" any other binding on the same port, including both IPv6 and IPv4
653
+ addresses. All other bindings for this port are removed, since :: already
654
+ covers all interfaces. For example, adding 127.0.0.1:4566, [::1]:4566,
655
+ and [::]:4566 would result in only [::]:4566 being preserved.
656
+ - 0.0.0.0 "trumps" any other binding on IPv4 addresses only. IPv6 addresses
657
+ are not removed.
658
+ - Identical identical hosts and ports are de-duped
659
+ """
660
+
661
+ def __init__(self, iterable: list[HostAndPort] | None = None):
662
+ super().__init__(iterable or [])
663
+ self._ensure_unique()
664
+
665
+ def _ensure_unique(self):
666
+ """
667
+ Ensure that all bindings on the same port are de-duped.
668
+ """
669
+ if len(self) <= 1:
670
+ return
671
+
672
+ unique: list[HostAndPort] = []
673
+
674
+ # Build a dictionary of hosts by port
675
+ hosts_by_port: dict[int, list[str]] = defaultdict(list)
676
+ for item in self:
677
+ hosts_by_port[item.port].append(item.host)
678
+
679
+ # For any given port, dedupe the hosts
680
+ for port, hosts in hosts_by_port.items():
681
+ deduped_hosts = set(hosts)
682
+
683
+ # IPv6 all interfaces: this is the most general binding.
684
+ # Any others should be removed.
685
+ if "::" in deduped_hosts:
686
+ unique.append(HostAndPort(host="::", port=port))
687
+ continue
688
+ # IPv4 all interfaces: this is the next most general binding.
689
+ # Any others should be removed.
690
+ if "0.0.0.0" in deduped_hosts:
691
+ unique.append(HostAndPort(host="0.0.0.0", port=port))
692
+ continue
693
+
694
+ # All other bindings just need to be unique
695
+ unique.extend([HostAndPort(host=host, port=port) for host in deduped_hosts])
696
+
697
+ self.clear()
698
+ self.extend(unique)
699
+
700
+ def append(self, value: HostAndPort):
701
+ super().append(value)
702
+ self._ensure_unique()
703
+
704
+
705
+ def populate_edge_configuration(
706
+ environment: Mapping[str, str],
707
+ ) -> tuple[HostAndPort, UniqueHostAndPortList]:
708
+ """Populate the LocalStack edge configuration from environment variables."""
709
+ localstack_host_raw = environment.get("LOCALSTACK_HOST")
710
+ gateway_listen_raw = environment.get("GATEWAY_LISTEN")
711
+
712
+ # parse gateway listen from multiple components
713
+ if gateway_listen_raw is not None:
714
+ gateway_listen = []
715
+ for address in gateway_listen_raw.split(","):
716
+ gateway_listen.append(
717
+ HostAndPort.parse(
718
+ address.strip(),
719
+ default_host=default_ip,
720
+ default_port=constants.DEFAULT_PORT_EDGE,
721
+ )
722
+ )
723
+ else:
724
+ # use default if gateway listen is not defined
725
+ gateway_listen = [HostAndPort(host=default_ip, port=constants.DEFAULT_PORT_EDGE)]
726
+
727
+ # the actual value of the LOCALSTACK_HOST port now depends on what gateway listen actually listens to.
728
+ if localstack_host_raw is None:
729
+ localstack_host = HostAndPort(
730
+ host=constants.LOCALHOST_HOSTNAME, port=gateway_listen[0].port
731
+ )
732
+ else:
733
+ localstack_host = HostAndPort.parse(
734
+ localstack_host_raw,
735
+ default_host=constants.LOCALHOST_HOSTNAME,
736
+ default_port=gateway_listen[0].port,
737
+ )
738
+
739
+ assert gateway_listen is not None
740
+ assert localstack_host is not None
741
+
742
+ return (
743
+ localstack_host,
744
+ UniqueHostAndPortList(gateway_listen),
745
+ )
746
+
747
+
748
+ # How to access LocalStack
749
+ (
750
+ # -- Cosmetic
751
+ LOCALSTACK_HOST,
752
+ # -- Edge configuration
753
+ # Main configuration of the listen address of the hypercorn proxy. Of the form
754
+ # <ip_address>:<port>(,<ip_address>:port>)*
755
+ GATEWAY_LISTEN,
756
+ ) = populate_edge_configuration(os.environ)
757
+
758
+ GATEWAY_WORKER_COUNT = int(os.environ.get("GATEWAY_WORKER_COUNT") or 1000)
759
+
760
+ # the gateway server that should be used (supported: hypercorn, twisted dev: werkzeug)
761
+ GATEWAY_SERVER = os.environ.get("GATEWAY_SERVER", "").strip() or "twisted"
762
+
763
+ # IP of the docker bridge used to enable access between containers
764
+ DOCKER_BRIDGE_IP = os.environ.get("DOCKER_BRIDGE_IP", "").strip()
765
+
766
+ # Default timeout for Docker API calls sent by the Docker SDK client, in seconds.
767
+ DOCKER_SDK_DEFAULT_TIMEOUT_SECONDS = int(os.environ.get("DOCKER_SDK_DEFAULT_TIMEOUT_SECONDS") or 60)
768
+
769
+ # Default number of retries to connect to the Docker API by the Docker SDK client.
770
+ DOCKER_SDK_DEFAULT_RETRIES = int(os.environ.get("DOCKER_SDK_DEFAULT_RETRIES") or 0)
771
+
772
+ # whether to enable API-based updates of configuration variables at runtime
773
+ ENABLE_CONFIG_UPDATES = is_env_true("ENABLE_CONFIG_UPDATES")
774
+
775
+ # CORS settings
776
+ DISABLE_CORS_HEADERS = is_env_true("DISABLE_CORS_HEADERS")
777
+ DISABLE_CORS_CHECKS = is_env_true("DISABLE_CORS_CHECKS")
778
+ DISABLE_CUSTOM_CORS_S3 = is_env_true("DISABLE_CUSTOM_CORS_S3")
779
+ DISABLE_CUSTOM_CORS_APIGATEWAY = is_env_true("DISABLE_CUSTOM_CORS_APIGATEWAY")
780
+ EXTRA_CORS_ALLOWED_HEADERS = os.environ.get("EXTRA_CORS_ALLOWED_HEADERS", "").strip()
781
+ EXTRA_CORS_EXPOSE_HEADERS = os.environ.get("EXTRA_CORS_EXPOSE_HEADERS", "").strip()
782
+ EXTRA_CORS_ALLOWED_ORIGINS = os.environ.get("EXTRA_CORS_ALLOWED_ORIGINS", "").strip()
783
+ DISABLE_PREFLIGHT_PROCESSING = is_env_true("DISABLE_PREFLIGHT_PROCESSING")
784
+
785
+ # whether to disable publishing events to the API
786
+ DISABLE_EVENTS = is_env_true("DISABLE_EVENTS")
787
+ DEBUG_ANALYTICS = is_env_true("DEBUG_ANALYTICS")
788
+
789
+ # whether to log fine-grained debugging information for the handler chain
790
+ DEBUG_HANDLER_CHAIN = is_env_true("DEBUG_HANDLER_CHAIN")
791
+
792
+ # whether to eagerly start services
793
+ EAGER_SERVICE_LOADING = is_env_true("EAGER_SERVICE_LOADING")
794
+
795
+ # whether to selectively load services in SERVICES
796
+ STRICT_SERVICE_LOADING = is_env_not_false("STRICT_SERVICE_LOADING")
797
+
798
+ # Whether to skip downloading additional infrastructure components (e.g., custom Elasticsearch versions)
799
+ SKIP_INFRA_DOWNLOADS = os.environ.get("SKIP_INFRA_DOWNLOADS", "").strip()
800
+
801
+ # Whether to skip downloading our signed SSL cert.
802
+ SKIP_SSL_CERT_DOWNLOAD = is_env_true("SKIP_SSL_CERT_DOWNLOAD")
803
+
804
+ # Absolute path to a custom certificate (pem file)
805
+ CUSTOM_SSL_CERT_PATH = os.environ.get("CUSTOM_SSL_CERT_PATH", "").strip()
806
+
807
+ # Whether delete the cached signed SSL certificate at startup
808
+ REMOVE_SSL_CERT = is_env_true("REMOVE_SSL_CERT")
809
+
810
+ # Allow non-standard AWS regions
811
+ ALLOW_NONSTANDARD_REGIONS = is_env_true("ALLOW_NONSTANDARD_REGIONS")
812
+ if ALLOW_NONSTANDARD_REGIONS:
813
+ os.environ["MOTO_ALLOW_NONEXISTENT_REGION"] = "true"
814
+
815
+ # name of the main Docker container
816
+ MAIN_CONTAINER_NAME = os.environ.get("MAIN_CONTAINER_NAME", "").strip() or "localstack-main"
817
+
818
+ # the latest commit id of the repository when the docker image was created
819
+ LOCALSTACK_BUILD_GIT_HASH = os.environ.get("LOCALSTACK_BUILD_GIT_HASH", "").strip() or None
820
+
821
+ # the date on which the docker image was created
822
+ LOCALSTACK_BUILD_DATE = os.environ.get("LOCALSTACK_BUILD_DATE", "").strip() or None
823
+
824
+ # Equivalent to HTTP_PROXY, but only applicable for external connections
825
+ OUTBOUND_HTTP_PROXY = os.environ.get("OUTBOUND_HTTP_PROXY", "")
826
+
827
+ # Equivalent to HTTPS_PROXY, but only applicable for external connections
828
+ OUTBOUND_HTTPS_PROXY = os.environ.get("OUTBOUND_HTTPS_PROXY", "")
829
+
830
+ # Feature flag to enable validation of internal endpoint responses in the handler chain. For test use only.
831
+ OPENAPI_VALIDATE_RESPONSE = is_env_true("OPENAPI_VALIDATE_RESPONSE")
832
+ # Flag to enable the validation of the requests made to the LocalStack internal endpoints. Active by default.
833
+ OPENAPI_VALIDATE_REQUEST = is_env_true("OPENAPI_VALIDATE_REQUEST")
834
+
835
+ # environment variable to determine whether to include stack traces in http responses
836
+ INCLUDE_STACK_TRACES_IN_HTTP_RESPONSE = is_env_true("INCLUDE_STACK_TRACES_IN_HTTP_RESPONSE")
837
+
838
+ # whether to skip waiting for the infrastructure to shut down, or exit immediately
839
+ FORCE_SHUTDOWN = is_env_not_false("FORCE_SHUTDOWN")
840
+
841
+ # set variables no_proxy, i.e., run internal service calls directly
842
+ no_proxy = ",".join([constants.LOCALHOST_HOSTNAME, LOCALHOST, LOCALHOST_IP, "[::1]"])
843
+ if os.environ.get("no_proxy"):
844
+ os.environ["no_proxy"] += "," + no_proxy
845
+ elif os.environ.get("NO_PROXY"):
846
+ os.environ["NO_PROXY"] += "," + no_proxy
847
+ else:
848
+ os.environ["no_proxy"] = no_proxy
849
+
850
+ # additional CLI commands, can be set by plugins
851
+ CLI_COMMANDS = {}
852
+
853
+ # determine IP of Docker bridge
854
+ if not DOCKER_BRIDGE_IP:
855
+ DOCKER_BRIDGE_IP = "172.17.0.1"
856
+ if is_in_docker:
857
+ candidates = (DOCKER_BRIDGE_IP, "172.18.0.1")
858
+ for ip in candidates:
859
+ # TODO: remove from here - should not perform I/O operations in top-level config.py
860
+ if ping(ip):
861
+ DOCKER_BRIDGE_IP = ip
862
+ break
863
+
864
+ # AWS account used to store internal resources such as Lambda archives or internal SQS queues.
865
+ # It should not be modified by the user, or visible to him, except as through a presigned url with the
866
+ # get-function call.
867
+ INTERNAL_RESOURCE_ACCOUNT = os.environ.get("INTERNAL_RESOURCE_ACCOUNT") or "949334387222"
868
+
869
+ # TODO: remove with 4.1.0
870
+ # Determine which implementation to use for the event rule / event filtering engine used by multiple services:
871
+ # EventBridge, EventBridge Pipes, Lambda Event Source Mapping
872
+ # Options: python (default) | java (deprecated since 4.0.3)
873
+ EVENT_RULE_ENGINE = os.environ.get("EVENT_RULE_ENGINE", "python").strip()
874
+
875
+ # -----
876
+ # SERVICE-SPECIFIC CONFIGS BELOW
877
+ # -----
878
+
879
+ # port ranges for external service instances (f.e. elasticsearch clusters, opensearch clusters,...)
880
+ EXTERNAL_SERVICE_PORTS_START = int(
881
+ os.environ.get("EXTERNAL_SERVICE_PORTS_START")
882
+ or os.environ.get("SERVICE_INSTANCES_PORTS_START")
883
+ or 4510
884
+ )
885
+ EXTERNAL_SERVICE_PORTS_END = int(
886
+ os.environ.get("EXTERNAL_SERVICE_PORTS_END")
887
+ or os.environ.get("SERVICE_INSTANCES_PORTS_END")
888
+ or (EXTERNAL_SERVICE_PORTS_START + 50)
889
+ )
890
+
891
+ # The default container runtime to use
892
+ CONTAINER_RUNTIME = os.environ.get("CONTAINER_RUNTIME", "").strip() or "docker"
893
+
894
+ # PUBLIC v1: -Xmx512M (example) Currently not supported in new provider but possible via custom entrypoint.
895
+ # Allow passing custom JVM options to Java Lambdas executed in Docker.
896
+ LAMBDA_JAVA_OPTS = os.environ.get("LAMBDA_JAVA_OPTS", "").strip()
897
+
898
+ # limit in which to kinesis-mock will start throwing exceptions
899
+ KINESIS_SHARD_LIMIT = os.environ.get("KINESIS_SHARD_LIMIT", "").strip() or "100"
900
+ KINESIS_PERSISTENCE = is_env_not_false("KINESIS_PERSISTENCE")
901
+
902
+ # limit in which to kinesis-mock will start throwing exceptions
903
+ KINESIS_ON_DEMAND_STREAM_COUNT_LIMIT = (
904
+ os.environ.get("KINESIS_ON_DEMAND_STREAM_COUNT_LIMIT", "").strip() or "10"
905
+ )
906
+
907
+ # delay in kinesis-mock response when making changes to streams
908
+ KINESIS_LATENCY = os.environ.get("KINESIS_LATENCY", "").strip() or "500"
909
+
910
+ # Delay between data persistence (in seconds)
911
+ KINESIS_MOCK_PERSIST_INTERVAL = os.environ.get("KINESIS_MOCK_PERSIST_INTERVAL", "").strip() or "5s"
912
+
913
+ # Kinesis mock log level override when inconsistent with LS_LOG (e.g., when LS_LOG=debug)
914
+ KINESIS_MOCK_LOG_LEVEL = os.environ.get("KINESIS_MOCK_LOG_LEVEL", "").strip()
915
+
916
+ # randomly inject faults to Kinesis
917
+ KINESIS_ERROR_PROBABILITY = float(os.environ.get("KINESIS_ERROR_PROBABILITY", "").strip() or 0.0)
918
+
919
+ # SEMI-PUBLIC: "node" (default); not actively communicated
920
+ # Select whether to use the node or scala build when running Kinesis Mock
921
+ KINESIS_MOCK_PROVIDER_ENGINE = os.environ.get("KINESIS_MOCK_PROVIDER_ENGINE", "").strip() or "node"
922
+
923
+ # set the maximum Java heap size corresponding to the '-Xmx<size>' flag
924
+ KINESIS_MOCK_MAXIMUM_HEAP_SIZE = (
925
+ os.environ.get("KINESIS_MOCK_MAXIMUM_HEAP_SIZE", "").strip() or "512m"
926
+ )
927
+
928
+ # set the initial Java heap size corresponding to the '-Xms<size>' flag
929
+ KINESIS_MOCK_INITIAL_HEAP_SIZE = (
930
+ os.environ.get("KINESIS_MOCK_INITIAL_HEAP_SIZE", "").strip() or "256m"
931
+ )
932
+
933
+ # randomly inject faults to DynamoDB
934
+ DYNAMODB_ERROR_PROBABILITY = float(os.environ.get("DYNAMODB_ERROR_PROBABILITY", "").strip() or 0.0)
935
+ DYNAMODB_READ_ERROR_PROBABILITY = float(
936
+ os.environ.get("DYNAMODB_READ_ERROR_PROBABILITY", "").strip() or 0.0
937
+ )
938
+ DYNAMODB_WRITE_ERROR_PROBABILITY = float(
939
+ os.environ.get("DYNAMODB_WRITE_ERROR_PROBABILITY", "").strip() or 0.0
940
+ )
941
+
942
+ # JAVA EE heap size for dynamodb
943
+ DYNAMODB_HEAP_SIZE = os.environ.get("DYNAMODB_HEAP_SIZE", "").strip() or "256m"
944
+
945
+ # single DB instance across multiple credentials are regions
946
+ DYNAMODB_SHARE_DB = int(os.environ.get("DYNAMODB_SHARE_DB") or 0)
947
+
948
+ # the port on which to expose dynamodblocal
949
+ DYNAMODB_LOCAL_PORT = int(os.environ.get("DYNAMODB_LOCAL_PORT") or 0)
950
+
951
+ # Enables the automatic removal of stale KV pais based on TTL
952
+ DYNAMODB_REMOVE_EXPIRED_ITEMS = is_env_true("DYNAMODB_REMOVE_EXPIRED_ITEMS")
953
+
954
+ # Used to toggle PurgeInProgress exceptions when calling purge within 60 seconds
955
+ SQS_DELAY_PURGE_RETRY = is_env_true("SQS_DELAY_PURGE_RETRY")
956
+
957
+ # Used to toggle QueueDeletedRecently errors when re-creating a queue within 60 seconds of deleting it
958
+ SQS_DELAY_RECENTLY_DELETED = is_env_true("SQS_DELAY_RECENTLY_DELETED")
959
+
960
+ # Used to toggle MessageRetentionPeriod functionality in SQS queues
961
+ SQS_ENABLE_MESSAGE_RETENTION_PERIOD = is_env_true("SQS_ENABLE_MESSAGE_RETENTION_PERIOD")
962
+
963
+ # Strategy used when creating SQS queue urls. can be "off", "standard" (default), "domain", "path", or "dynamic"
964
+ SQS_ENDPOINT_STRATEGY = os.environ.get("SQS_ENDPOINT_STRATEGY", "") or "standard"
965
+
966
+ # Disable the check for MaxNumberOfMessage in SQS ReceiveMessage
967
+ SQS_DISABLE_MAX_NUMBER_OF_MESSAGE_LIMIT = is_env_true("SQS_DISABLE_MAX_NUMBER_OF_MESSAGE_LIMIT")
968
+
969
+ # Disable cloudwatch metrics for SQS
970
+ SQS_DISABLE_CLOUDWATCH_METRICS = is_env_true("SQS_DISABLE_CLOUDWATCH_METRICS")
971
+
972
+ # Interval for reporting "approximate" metrics to cloudwatch, default is 60 seconds
973
+ SQS_CLOUDWATCH_METRICS_REPORT_INTERVAL = int(
974
+ os.environ.get("SQS_CLOUDWATCH_METRICS_REPORT_INTERVAL") or 60
975
+ )
976
+
977
+ # PUBLIC: Endpoint host under which LocalStack APIs are accessible from Lambda Docker containers.
978
+ HOSTNAME_FROM_LAMBDA = os.environ.get("HOSTNAME_FROM_LAMBDA", "").strip()
979
+
980
+ # PUBLIC: hot-reload (default v2), __local__ (default v1)
981
+ # Magic S3 bucket name for Hot Reloading. The S3Key points to the source code on the local file system.
982
+ BUCKET_MARKER_LOCAL = (
983
+ os.environ.get("BUCKET_MARKER_LOCAL", "").strip() or DEFAULT_BUCKET_MARKER_LOCAL
984
+ )
985
+
986
+ # PUBLIC: Opt-out to inject the environment variable AWS_ENDPOINT_URL for automatic configuration of AWS SDKs:
987
+ # https://docs.aws.amazon.com/sdkref/latest/guide/feature-ss-endpoints.html
988
+ LAMBDA_DISABLE_AWS_ENDPOINT_URL = is_env_true("LAMBDA_DISABLE_AWS_ENDPOINT_URL")
989
+
990
+ # PUBLIC: bridge (Docker default)
991
+ # Docker network driver for the Lambda and ECS containers. https://docs.docker.com/network/
992
+ LAMBDA_DOCKER_NETWORK = os.environ.get("LAMBDA_DOCKER_NETWORK", "").strip()
993
+
994
+ # PUBLIC v1: LocalStack DNS (default)
995
+ # Custom DNS server for the container running your lambda function.
996
+ LAMBDA_DOCKER_DNS = os.environ.get("LAMBDA_DOCKER_DNS", "").strip()
997
+
998
+ # PUBLIC: -e KEY=VALUE -v host:container
999
+ # Additional flags passed to Docker run|create commands.
1000
+ LAMBDA_DOCKER_FLAGS = os.environ.get("LAMBDA_DOCKER_FLAGS", "").strip()
1001
+
1002
+ # PUBLIC: 0 (default)
1003
+ # Enable this flag to run cross-platform compatible lambda functions natively (i.e., Docker selects architecture) and
1004
+ # ignore the AWS architectures (i.e., x86_64, arm64) configured for the lambda function.
1005
+ LAMBDA_IGNORE_ARCHITECTURE = is_env_true("LAMBDA_IGNORE_ARCHITECTURE")
1006
+
1007
+ # TODO: test and add to docs
1008
+ # EXPERIMENTAL: 0 (default)
1009
+ # prebuild images before execution? Increased cold start time on the tradeoff of increased time until lambda is ACTIVE
1010
+ LAMBDA_PREBUILD_IMAGES = is_env_true("LAMBDA_PREBUILD_IMAGES")
1011
+
1012
+ # PUBLIC: docker (default), kubernetes (pro)
1013
+ # Where Lambdas will be executed.
1014
+ LAMBDA_RUNTIME_EXECUTOR = os.environ.get("LAMBDA_RUNTIME_EXECUTOR", CONTAINER_RUNTIME).strip()
1015
+
1016
+ # PUBLIC: 20 (default)
1017
+ # How many seconds Lambda will wait for the runtime environment to start up.
1018
+ LAMBDA_RUNTIME_ENVIRONMENT_TIMEOUT = int(os.environ.get("LAMBDA_RUNTIME_ENVIRONMENT_TIMEOUT") or 20)
1019
+
1020
+ # PUBLIC: base images for Lambda (default) https://docs.aws.amazon.com/lambda/latest/dg/runtimes-images.html
1021
+ # localstack/services/lambda_/invocation/lambda_models.py:IMAGE_MAPPING
1022
+ # Customize the Docker image of Lambda runtimes, either by:
1023
+ # a) pattern with <runtime> placeholder, e.g. custom-repo/lambda-<runtime>:2022
1024
+ # b) json dict mapping the <runtime> to an image, e.g. {"python3.9": "custom-repo/lambda-py:thon3.9"}
1025
+ LAMBDA_RUNTIME_IMAGE_MAPPING = os.environ.get("LAMBDA_RUNTIME_IMAGE_MAPPING", "").strip()
1026
+
1027
+
1028
+ # PUBLIC: 0 (default)
1029
+ # Whether to disable usage of deprecated runtimes
1030
+ LAMBDA_RUNTIME_VALIDATION = int(os.environ.get("LAMBDA_RUNTIME_VALIDATION") or 0)
1031
+
1032
+ # PUBLIC: 1 (default)
1033
+ # Whether to remove any Lambda Docker containers.
1034
+ LAMBDA_REMOVE_CONTAINERS = (
1035
+ os.environ.get("LAMBDA_REMOVE_CONTAINERS", "").lower().strip() not in FALSE_STRINGS
1036
+ )
1037
+
1038
+ # PUBLIC: 600000 (default 10min)
1039
+ # Time in milliseconds until lambda shuts down the execution environment after the last invocation has been processed.
1040
+ # Set to 0 to immediately shut down the execution environment after an invocation.
1041
+ LAMBDA_KEEPALIVE_MS = int(os.environ.get("LAMBDA_KEEPALIVE_MS", 600_000))
1042
+
1043
+ # PUBLIC: 1000 (default)
1044
+ # The maximum number of events that functions can process simultaneously in the current Region.
1045
+ # See AWS service quotas: https://docs.aws.amazon.com/general/latest/gr/lambda-service.html
1046
+ # Concurrency limits. Like on AWS these apply per account and region.
1047
+ LAMBDA_LIMITS_CONCURRENT_EXECUTIONS = int(
1048
+ os.environ.get("LAMBDA_LIMITS_CONCURRENT_EXECUTIONS", 1_000)
1049
+ )
1050
+ # SEMI-PUBLIC: not actively communicated
1051
+ # per account/region: there must be at least <LAMBDA_LIMITS_MINIMUM_UNRESERVED_CONCURRENCY> unreserved concurrency.
1052
+ LAMBDA_LIMITS_MINIMUM_UNRESERVED_CONCURRENCY = int(
1053
+ os.environ.get("LAMBDA_LIMITS_MINIMUM_UNRESERVED_CONCURRENCY", 100)
1054
+ )
1055
+ # SEMI-PUBLIC: not actively communicated
1056
+ LAMBDA_LIMITS_TOTAL_CODE_SIZE = int(os.environ.get("LAMBDA_LIMITS_TOTAL_CODE_SIZE", 80_530_636_800))
1057
+ # PUBLIC: documented after AWS changed validation around 2023-11
1058
+ LAMBDA_LIMITS_CODE_SIZE_ZIPPED = int(os.environ.get("LAMBDA_LIMITS_CODE_SIZE_ZIPPED", 52_428_800))
1059
+ # SEMI-PUBLIC: not actively communicated
1060
+ LAMBDA_LIMITS_CODE_SIZE_UNZIPPED = int(
1061
+ os.environ.get("LAMBDA_LIMITS_CODE_SIZE_UNZIPPED", 262_144_000)
1062
+ )
1063
+ # PUBLIC: documented upon customer request
1064
+ LAMBDA_LIMITS_CREATE_FUNCTION_REQUEST_SIZE = int(
1065
+ os.environ.get("LAMBDA_LIMITS_CREATE_FUNCTION_REQUEST_SIZE", 70_167_211)
1066
+ )
1067
+ # SEMI-PUBLIC: not actively communicated
1068
+ LAMBDA_LIMITS_MAX_FUNCTION_ENVVAR_SIZE_BYTES = int(
1069
+ os.environ.get("LAMBDA_LIMITS_MAX_FUNCTION_ENVVAR_SIZE_BYTES", 4 * 1024)
1070
+ )
1071
+ # SEMI-PUBLIC: not actively communicated
1072
+ LAMBDA_LIMITS_MAX_FUNCTION_PAYLOAD_SIZE_BYTES = int(
1073
+ os.environ.get(
1074
+ "LAMBDA_LIMITS_MAX_FUNCTION_PAYLOAD_SIZE_BYTES", 6 * 1024 * 1024 + 100
1075
+ ) # the 100 comes from the init defaults
1076
+ )
1077
+
1078
+ # DEV: 0 (default unless in host mode on macOS) For LS developers only. Only applies to Docker mode.
1079
+ # Whether to explicitly expose a free TCP port in lambda containers when invoking functions in host mode for
1080
+ # systems that cannot reach the container via its IPv4. For example, macOS cannot reach Docker containers:
1081
+ # https://docs.docker.com/desktop/networking/#i-cannot-ping-my-containers
1082
+ LAMBDA_DEV_PORT_EXPOSE = (
1083
+ # Enable this dev flag by default on macOS in host mode (i.e., non-Docker environment)
1084
+ is_env_not_false("LAMBDA_DEV_PORT_EXPOSE")
1085
+ if not is_in_docker and is_in_macos
1086
+ else is_env_true("LAMBDA_DEV_PORT_EXPOSE")
1087
+ )
1088
+
1089
+ # DEV: only applies to new lambda provider. All LAMBDA_INIT_* configuration are for LS developers only.
1090
+ # There are NO stability guarantees, and they may break at any time.
1091
+
1092
+ # DEV: Release version of https://github.com/localstack/lambda-runtime-init overriding the current default
1093
+ LAMBDA_INIT_RELEASE_VERSION = os.environ.get("LAMBDA_INIT_RELEASE_VERSION")
1094
+ # DEV: 0 (default) Enable for mounting of RIE init binary and delve debugger
1095
+ LAMBDA_INIT_DEBUG = is_env_true("LAMBDA_INIT_DEBUG")
1096
+ # DEV: path to RIE init binary (e.g., var/rapid/init)
1097
+ LAMBDA_INIT_BIN_PATH = os.environ.get("LAMBDA_INIT_BIN_PATH")
1098
+ # DEV: path to entrypoint script (e.g., var/rapid/entrypoint.sh)
1099
+ LAMBDA_INIT_BOOTSTRAP_PATH = os.environ.get("LAMBDA_INIT_BOOTSTRAP_PATH")
1100
+ # DEV: path to delve debugger (e.g., var/rapid/dlv)
1101
+ LAMBDA_INIT_DELVE_PATH = os.environ.get("LAMBDA_INIT_DELVE_PATH")
1102
+ # DEV: Go Delve debug port
1103
+ LAMBDA_INIT_DELVE_PORT = int(os.environ.get("LAMBDA_INIT_DELVE_PORT") or 40000)
1104
+ # DEV: Time to wait after every invoke as a workaround to fix a race condition in persistence tests
1105
+ LAMBDA_INIT_POST_INVOKE_WAIT_MS = os.environ.get("LAMBDA_INIT_POST_INVOKE_WAIT_MS")
1106
+ # DEV: sbx_user1051 (default when not provided) Alternative system user or empty string to skip dropping privileges.
1107
+ LAMBDA_INIT_USER = os.environ.get("LAMBDA_INIT_USER")
1108
+
1109
+ # INTERNAL: 1 (default)
1110
+ # The duration (in seconds) to wait between each poll call to an event source.
1111
+ LAMBDA_EVENT_SOURCE_MAPPING_POLL_INTERVAL_SEC = float(
1112
+ os.environ.get("LAMBDA_EVENT_SOURCE_MAPPING_POLL_INTERVAL_SEC") or 1
1113
+ )
1114
+
1115
+ # INTERNAL: 60 (default)
1116
+ # Maximum duration (in seconds) to wait between retries when an event source poll fails.
1117
+ LAMBDA_EVENT_SOURCE_MAPPING_MAX_BACKOFF_ON_ERROR_SEC = float(
1118
+ os.environ.get("LAMBDA_EVENT_SOURCE_MAPPING_MAX_BACKOFF_ON_ERROR_SEC") or 60
1119
+ )
1120
+
1121
+ # INTERNAL: 10 (default)
1122
+ # Maximum duration (in seconds) to wait between polls when an event source returns empty results.
1123
+ LAMBDA_EVENT_SOURCE_MAPPING_MAX_BACKOFF_ON_EMPTY_POLL_SEC = float(
1124
+ os.environ.get("LAMBDA_EVENT_SOURCE_MAPPING_MAX_BACKOFF_ON_EMPTY_POLL_SEC") or 10
1125
+ )
1126
+
1127
+ # Specifies the path to the mock configuration file for Step Functions, commonly named MockConfigFile.json.
1128
+ SFN_MOCK_CONFIG = os.environ.get("SFN_MOCK_CONFIG", "").strip()
1129
+
1130
+ # path prefix for windows volume mounting
1131
+ WINDOWS_DOCKER_MOUNT_PREFIX = os.environ.get("WINDOWS_DOCKER_MOUNT_PREFIX", "/host_mnt")
1132
+
1133
+ # whether to skip S3 presign URL signature validation (TODO: currently enabled, until all issues are resolved)
1134
+ S3_SKIP_SIGNATURE_VALIDATION = is_env_not_false("S3_SKIP_SIGNATURE_VALIDATION")
1135
+ # whether to skip S3 validation of provided KMS key
1136
+ S3_SKIP_KMS_KEY_VALIDATION = is_env_not_false("S3_SKIP_KMS_KEY_VALIDATION")
1137
+
1138
+ # PUBLIC: 2000 (default)
1139
+ # Allows increasing the default char limit for truncation of lambda log lines when printed in the console.
1140
+ # This does not affect the logs processing in CloudWatch.
1141
+ LAMBDA_TRUNCATE_STDOUT = int(os.getenv("LAMBDA_TRUNCATE_STDOUT") or 2000)
1142
+
1143
+ # INTERNAL: 60 (default matching AWS) only applies to new lambda provider
1144
+ # Base delay in seconds for async retries. Further retries use: NUM_ATTEMPTS * LAMBDA_RETRY_BASE_DELAY_SECONDS
1145
+ # 300 (5min) is the maximum because NUM_ATTEMPTS can be at most 3 and SQS has a message timer limit of 15 min.
1146
+ # For example:
1147
+ # 1x LAMBDA_RETRY_BASE_DELAY_SECONDS: delay between initial invocation and first retry
1148
+ # 2x LAMBDA_RETRY_BASE_DELAY_SECONDS: delay between the first retry and the second retry
1149
+ # 3x LAMBDA_RETRY_BASE_DELAY_SECONDS: delay between the second retry and the third retry
1150
+ LAMBDA_RETRY_BASE_DELAY_SECONDS = int(os.getenv("LAMBDA_RETRY_BASE_DELAY") or 60)
1151
+
1152
+ # PUBLIC: 0 (default)
1153
+ # Set to 1 to create lambda functions synchronously (not recommended).
1154
+ # Whether Lambda.CreateFunction will block until the function is in a terminal state (Active or Failed).
1155
+ # This technically breaks behavior parity but is provided as a simplification over the default AWS behavior and
1156
+ # to match the behavior of the old lambda provider.
1157
+ LAMBDA_SYNCHRONOUS_CREATE = is_env_true("LAMBDA_SYNCHRONOUS_CREATE")
1158
+
1159
+ # URL to a custom OpenSearch/Elasticsearch backend cluster. If this is set to a valid URL, then localstack will not
1160
+ # create OpenSearch/Elasticsearch cluster instances, but instead forward all domains to the given backend.
1161
+ OPENSEARCH_CUSTOM_BACKEND = os.environ.get("OPENSEARCH_CUSTOM_BACKEND", "").strip()
1162
+
1163
+ # Strategy used when creating OpenSearch/Elasticsearch domain endpoints routed through the edge proxy
1164
+ # valid values: domain | path | port (off)
1165
+ OPENSEARCH_ENDPOINT_STRATEGY = (
1166
+ os.environ.get("OPENSEARCH_ENDPOINT_STRATEGY", "").strip() or "domain"
1167
+ )
1168
+ if OPENSEARCH_ENDPOINT_STRATEGY == "off":
1169
+ OPENSEARCH_ENDPOINT_STRATEGY = "port"
1170
+
1171
+ # Whether to start one cluster per domain (default), or multiplex opensearch domains to a single clusters
1172
+ OPENSEARCH_MULTI_CLUSTER = is_env_not_false("OPENSEARCH_MULTI_CLUSTER")
1173
+
1174
+ # Whether to really publish to GCM while using SNS Platform Application (needs credentials)
1175
+ LEGACY_SNS_GCM_PUBLISHING = is_env_true("LEGACY_SNS_GCM_PUBLISHING")
1176
+
1177
+ SNS_SES_SENDER_ADDRESS = os.environ.get("SNS_SES_SENDER_ADDRESS", "").strip()
1178
+
1179
+ SNS_CERT_URL_HOST = os.environ.get("SNS_CERT_URL_HOST", "").strip()
1180
+
1181
+ # Whether the Next Gen APIGW invocation logic is enabled (on by default)
1182
+ APIGW_NEXT_GEN_PROVIDER = os.environ.get("PROVIDER_OVERRIDE_APIGATEWAY", "") in ("next_gen", "")
1183
+
1184
+ # Whether the DynamoDBStreams native provider is enabled
1185
+ DDB_STREAMS_PROVIDER_V2 = os.environ.get("PROVIDER_OVERRIDE_DYNAMODBSTREAMS", "") == "v2"
1186
+ _override_dynamodb_v2 = os.environ.get("PROVIDER_OVERRIDE_DYNAMODB", "")
1187
+ if DDB_STREAMS_PROVIDER_V2:
1188
+ # in order to not have conflicts between the 2 implementations, as they are tightly coupled, we need to set DDB
1189
+ # to be v2 as well
1190
+ if not _override_dynamodb_v2:
1191
+ os.environ["PROVIDER_OVERRIDE_DYNAMODB"] = "v2"
1192
+ elif _override_dynamodb_v2 == "v2":
1193
+ os.environ["PROVIDER_OVERRIDE_DYNAMODBSTREAMS"] = "v2"
1194
+ DDB_STREAMS_PROVIDER_V2 = True
1195
+
1196
+ SNS_PROVIDER_V2 = os.environ.get("PROVIDER_OVERRIDE_SNS", "") == "v2"
1197
+
1198
+ # TODO remove fallback to LAMBDA_DOCKER_NETWORK with next minor version
1199
+ MAIN_DOCKER_NETWORK = os.environ.get("MAIN_DOCKER_NETWORK", "") or LAMBDA_DOCKER_NETWORK
1200
+
1201
+ # Whether to return and parse access key ids starting with an "A", like on AWS
1202
+ PARITY_AWS_ACCESS_KEY_ID = is_env_true("PARITY_AWS_ACCESS_KEY_ID")
1203
+
1204
+ # Show exceptions for CloudFormation deploy errors
1205
+ CFN_VERBOSE_ERRORS = is_env_true("CFN_VERBOSE_ERRORS")
1206
+
1207
+ # The CFN_STRING_REPLACEMENT_DENY_LIST env variable is a comma separated list of strings that are not allowed to be
1208
+ # replaced in CloudFormation templates (e.g. AWS URLs that are usually edited by Localstack to point to itself if found
1209
+ # in a CFN template). They are extracted to a list of strings if the env variable is set.
1210
+ CFN_STRING_REPLACEMENT_DENY_LIST = [
1211
+ x for x in os.environ.get("CFN_STRING_REPLACEMENT_DENY_LIST", "").split(",") if x
1212
+ ]
1213
+
1214
+ # Set the timeout to deploy each individual CloudFormation resource
1215
+ CFN_PER_RESOURCE_TIMEOUT = int(os.environ.get("CFN_PER_RESOURCE_TIMEOUT") or 300)
1216
+
1217
+ # How localstack will react to encountering unsupported resource types.
1218
+ # By default unsupported resource types will be ignored.
1219
+ # EXPERIMENTAL
1220
+ CFN_IGNORE_UNSUPPORTED_RESOURCE_TYPES = is_env_not_false("CFN_IGNORE_UNSUPPORTED_RESOURCE_TYPES")
1221
+
1222
+ # Comma-separated list of resource type names that CloudFormation will ignore on stack creation
1223
+ CFN_IGNORE_UNSUPPORTED_TYPE_CREATE = parse_comma_separated_list(
1224
+ "CFN_IGNORE_UNSUPPORTED_TYPE_CREATE"
1225
+ )
1226
+ # Comma-separated list of resource type names that CloudFormation will ignore on stack update
1227
+ CFN_IGNORE_UNSUPPORTED_TYPE_UPDATE = parse_comma_separated_list(
1228
+ "CFN_IGNORE_UNSUPPORTED_TYPE_UPDATE"
1229
+ )
1230
+
1231
+ # Decrease the waiting time for resource deployment
1232
+ CFN_NO_WAIT_ITERATIONS: str | int | None = os.environ.get("CFN_NO_WAIT_ITERATIONS")
1233
+
1234
+ # bind address of local DNS server
1235
+ DNS_ADDRESS = os.environ.get("DNS_ADDRESS") or "0.0.0.0"
1236
+ # port of the local DNS server
1237
+ DNS_PORT = int(os.environ.get("DNS_PORT", "53"))
1238
+
1239
+ # Comma-separated list of regex patterns for DNS names to resolve locally.
1240
+ # Any DNS name not matched against any of the patterns on this whitelist
1241
+ # will resolve it to the real DNS entry, rather than the local one.
1242
+ DNS_NAME_PATTERNS_TO_RESOLVE_UPSTREAM = (
1243
+ os.environ.get("DNS_NAME_PATTERNS_TO_RESOLVE_UPSTREAM") or ""
1244
+ ).strip()
1245
+ DNS_LOCAL_NAME_PATTERNS = (os.environ.get("DNS_LOCAL_NAME_PATTERNS") or "").strip() # deprecated
1246
+
1247
+ # IP address that AWS endpoints should resolve to in our local DNS server. By default,
1248
+ # hostnames resolve to 127.0.0.1, which allows to use the LocalStack APIs transparently
1249
+ # from the host machine. If your code is running in Docker, this should be configured
1250
+ # to resolve to the Docker bridge network address, e.g., DNS_RESOLVE_IP=172.17.0.1
1251
+ DNS_RESOLVE_IP = os.environ.get("DNS_RESOLVE_IP") or LOCALHOST_IP
1252
+
1253
+ # fallback DNS server to send upstream requests to
1254
+ DNS_SERVER = os.environ.get("DNS_SERVER")
1255
+ DNS_VERIFICATION_DOMAIN = os.environ.get("DNS_VERIFICATION_DOMAIN") or "localstack.cloud"
1256
+
1257
+
1258
+ def use_custom_dns():
1259
+ return str(DNS_ADDRESS) not in FALSE_STRINGS
1260
+
1261
+
1262
+ # s3 virtual host name
1263
+ S3_VIRTUAL_HOSTNAME = f"s3.{LOCALSTACK_HOST.host}"
1264
+ S3_STATIC_WEBSITE_HOSTNAME = f"s3-website.{LOCALSTACK_HOST.host}"
1265
+
1266
+ BOTO_WAITER_DELAY = int(os.environ.get("BOTO_WAITER_DELAY") or "1")
1267
+ BOTO_WAITER_MAX_ATTEMPTS = int(os.environ.get("BOTO_WAITER_MAX_ATTEMPTS") or "120")
1268
+ DISABLE_CUSTOM_BOTO_WAITER_CONFIG = is_env_true("DISABLE_CUSTOM_BOTO_WAITER_CONFIG")
1269
+
1270
+ # defaults to false
1271
+ # if `DISABLE_BOTO_RETRIES=1` is set, all our created boto clients will have retries disabled
1272
+ DISABLE_BOTO_RETRIES = is_env_true("DISABLE_BOTO_RETRIES")
1273
+
1274
+ DISTRIBUTED_MODE = is_env_true("DISTRIBUTED_MODE")
1275
+
1276
+ # This flag enables `connect_to` to be in-memory only and not do networking calls
1277
+ IN_MEMORY_CLIENT = is_env_true("IN_MEMORY_CLIENT")
1278
+
1279
+ # This flag enables all responses from LocalStack to contain a `x-localstack` HTTP header.
1280
+ LOCALSTACK_RESPONSE_HEADER_ENABLED = is_env_not_false("LOCALSTACK_RESPONSE_HEADER_ENABLED")
1281
+
1282
+ # Serialization backend for the LocalStack internal state (`dill` is used by default`).
1283
+ STATE_SERIALIZATION_BACKEND = os.environ.get("STATE_SERIALIZATION_BACKEND", "").strip() or "dill"
1284
+
1285
+ # List of environment variable names used for configuration that are passed from the host into the LocalStack container.
1286
+ # => Synchronize this list with the above and the configuration docs:
1287
+ # https://docs.localstack.cloud/references/configuration/
1288
+ # => Sort this list alphabetically
1289
+ # => Add deprecated environment variables to deprecations.py and add a comment in this list
1290
+ # => Move removed legacy variables to the section grouped by release (still relevant for deprecation warnings)
1291
+ # => Do *not* include any internal developer configurations that apply to host-mode only in this list.
1292
+ CONFIG_ENV_VARS = [
1293
+ "ALLOW_NONSTANDARD_REGIONS",
1294
+ "BOTO_WAITER_DELAY",
1295
+ "BOTO_WAITER_MAX_ATTEMPTS",
1296
+ "BUCKET_MARKER_LOCAL",
1297
+ "CFN_IGNORE_UNSUPPORTED_RESOURCE_TYPES",
1298
+ "CFN_PER_RESOURCE_TIMEOUT",
1299
+ "CFN_STRING_REPLACEMENT_DENY_LIST",
1300
+ "CFN_VERBOSE_ERRORS",
1301
+ "CI",
1302
+ "CONTAINER_RUNTIME",
1303
+ "CUSTOM_SSL_CERT_PATH",
1304
+ "DEBUG",
1305
+ "DEBUG_HANDLER_CHAIN",
1306
+ "DEVELOP",
1307
+ "DEVELOP_PORT",
1308
+ "DISABLE_BOTO_RETRIES",
1309
+ "DISABLE_CORS_CHECKS",
1310
+ "DISABLE_CORS_HEADERS",
1311
+ "DISABLE_CUSTOM_BOTO_WAITER_CONFIG",
1312
+ "DISABLE_CUSTOM_CORS_APIGATEWAY",
1313
+ "DISABLE_CUSTOM_CORS_S3",
1314
+ "DISABLE_EVENTS",
1315
+ "DISTRIBUTED_MODE",
1316
+ "DNS_ADDRESS",
1317
+ "DNS_PORT",
1318
+ "DNS_LOCAL_NAME_PATTERNS",
1319
+ "DNS_NAME_PATTERNS_TO_RESOLVE_UPSTREAM",
1320
+ "DNS_RESOLVE_IP",
1321
+ "DNS_SERVER",
1322
+ "DNS_VERIFICATION_DOMAIN",
1323
+ "DOCKER_BRIDGE_IP",
1324
+ "DOCKER_SDK_DEFAULT_TIMEOUT_SECONDS",
1325
+ "DYNAMODB_ERROR_PROBABILITY",
1326
+ "DYNAMODB_HEAP_SIZE",
1327
+ "DYNAMODB_IN_MEMORY",
1328
+ "DYNAMODB_LOCAL_PORT",
1329
+ "DYNAMODB_SHARE_DB",
1330
+ "DYNAMODB_READ_ERROR_PROBABILITY",
1331
+ "DYNAMODB_REMOVE_EXPIRED_ITEMS",
1332
+ "DYNAMODB_WRITE_ERROR_PROBABILITY",
1333
+ "EAGER_SERVICE_LOADING",
1334
+ "ENABLE_CONFIG_UPDATES",
1335
+ "EVENT_RULE_ENGINE",
1336
+ "EXTRA_CORS_ALLOWED_HEADERS",
1337
+ "EXTRA_CORS_ALLOWED_ORIGINS",
1338
+ "EXTRA_CORS_EXPOSE_HEADERS",
1339
+ "GATEWAY_LISTEN",
1340
+ "GATEWAY_SERVER",
1341
+ "GATEWAY_WORKER_THREAD_COUNT",
1342
+ "HOSTNAME",
1343
+ "HOSTNAME_FROM_LAMBDA",
1344
+ "IN_MEMORY_CLIENT",
1345
+ "KINESIS_ERROR_PROBABILITY",
1346
+ "KINESIS_MOCK_PERSIST_INTERVAL",
1347
+ "KINESIS_MOCK_LOG_LEVEL",
1348
+ "KINESIS_ON_DEMAND_STREAM_COUNT_LIMIT",
1349
+ "KINESIS_PERSISTENCE",
1350
+ "LAMBDA_DEBUG_MODE",
1351
+ "LAMBDA_DEBUG_MODE_CONFIG",
1352
+ "LAMBDA_DISABLE_AWS_ENDPOINT_URL",
1353
+ "LAMBDA_DOCKER_DNS",
1354
+ "LAMBDA_DOCKER_FLAGS",
1355
+ "LAMBDA_DOCKER_NETWORK",
1356
+ "LAMBDA_EVENTS_INTERNAL_SQS",
1357
+ "LAMBDA_EVENT_SOURCE_MAPPING",
1358
+ "LAMBDA_IGNORE_ARCHITECTURE",
1359
+ "LAMBDA_INIT_DEBUG",
1360
+ "LAMBDA_INIT_BIN_PATH",
1361
+ "LAMBDA_INIT_BOOTSTRAP_PATH",
1362
+ "LAMBDA_INIT_DELVE_PATH",
1363
+ "LAMBDA_INIT_DELVE_PORT",
1364
+ "LAMBDA_INIT_POST_INVOKE_WAIT_MS",
1365
+ "LAMBDA_INIT_USER",
1366
+ "LAMBDA_INIT_RELEASE_VERSION",
1367
+ "LAMBDA_KEEPALIVE_MS",
1368
+ "LAMBDA_LIMITS_CONCURRENT_EXECUTIONS",
1369
+ "LAMBDA_LIMITS_MINIMUM_UNRESERVED_CONCURRENCY",
1370
+ "LAMBDA_LIMITS_TOTAL_CODE_SIZE",
1371
+ "LAMBDA_LIMITS_CODE_SIZE_ZIPPED",
1372
+ "LAMBDA_LIMITS_CODE_SIZE_UNZIPPED",
1373
+ "LAMBDA_LIMITS_CREATE_FUNCTION_REQUEST_SIZE",
1374
+ "LAMBDA_LIMITS_MAX_FUNCTION_ENVVAR_SIZE_BYTES",
1375
+ "LAMBDA_LIMITS_MAX_FUNCTION_PAYLOAD_SIZE_BYTES",
1376
+ "LAMBDA_PREBUILD_IMAGES",
1377
+ "LAMBDA_RUNTIME_IMAGE_MAPPING",
1378
+ "LAMBDA_REMOVE_CONTAINERS",
1379
+ "LAMBDA_RETRY_BASE_DELAY_SECONDS",
1380
+ "LAMBDA_RUNTIME_EXECUTOR",
1381
+ "LAMBDA_RUNTIME_ENVIRONMENT_TIMEOUT",
1382
+ "LAMBDA_RUNTIME_VALIDATION",
1383
+ "LAMBDA_SYNCHRONOUS_CREATE",
1384
+ "LAMBDA_SQS_EVENT_SOURCE_MAPPING_INTERVAL",
1385
+ "LAMBDA_TRUNCATE_STDOUT",
1386
+ "LEGACY_DOCKER_CLIENT",
1387
+ "LEGACY_SNS_GCM_PUBLISHING",
1388
+ "LOCALSTACK_API_KEY",
1389
+ "LOCALSTACK_AUTH_TOKEN",
1390
+ "LOCALSTACK_HOST",
1391
+ "LOCALSTACK_RESPONSE_HEADER_ENABLED",
1392
+ "LOG_LICENSE_ISSUES",
1393
+ "LS_LOG",
1394
+ "MAIN_CONTAINER_NAME",
1395
+ "MAIN_DOCKER_NETWORK",
1396
+ "OPENAPI_VALIDATE_REQUEST",
1397
+ "OPENAPI_VALIDATE_RESPONSE",
1398
+ "OPENSEARCH_ENDPOINT_STRATEGY",
1399
+ "OUTBOUND_HTTP_PROXY",
1400
+ "OUTBOUND_HTTPS_PROXY",
1401
+ "PARITY_AWS_ACCESS_KEY_ID",
1402
+ "PERSISTENCE",
1403
+ "PORTS_CHECK_DOCKER_IMAGE",
1404
+ "REQUESTS_CA_BUNDLE",
1405
+ "REMOVE_SSL_CERT",
1406
+ "S3_SKIP_SIGNATURE_VALIDATION",
1407
+ "S3_SKIP_KMS_KEY_VALIDATION",
1408
+ "SERVICES",
1409
+ "SKIP_INFRA_DOWNLOADS",
1410
+ "SKIP_SSL_CERT_DOWNLOAD",
1411
+ "SNAPSHOT_LOAD_STRATEGY",
1412
+ "SNAPSHOT_SAVE_STRATEGY",
1413
+ "SNAPSHOT_FLUSH_INTERVAL",
1414
+ "SNS_SES_SENDER_ADDRESS",
1415
+ "SQS_DELAY_PURGE_RETRY",
1416
+ "SQS_DELAY_RECENTLY_DELETED",
1417
+ "SQS_ENABLE_MESSAGE_RETENTION_PERIOD",
1418
+ "SQS_ENDPOINT_STRATEGY",
1419
+ "SQS_DISABLE_CLOUDWATCH_METRICS",
1420
+ "SQS_CLOUDWATCH_METRICS_REPORT_INTERVAL",
1421
+ "STATE_SERIALIZATION_BACKEND",
1422
+ "STRICT_SERVICE_LOADING",
1423
+ "TF_COMPAT_MODE",
1424
+ "USE_SSL",
1425
+ "WAIT_FOR_DEBUGGER",
1426
+ "WINDOWS_DOCKER_MOUNT_PREFIX",
1427
+ # Removed legacy variables in 2.0.0
1428
+ # DATA_DIR => do *not* include in this list, as it is treated separately. # deprecated since 1.0.0
1429
+ "LEGACY_DIRECTORIES", # deprecated since 1.0.0
1430
+ "SYNCHRONOUS_API_GATEWAY_EVENTS", # deprecated since 1.3.0
1431
+ "SYNCHRONOUS_DYNAMODB_EVENTS", # deprecated since 1.3.0
1432
+ "SYNCHRONOUS_SNS_EVENTS", # deprecated since 1.3.0
1433
+ "SYNCHRONOUS_SQS_EVENTS", # deprecated since 1.3.0
1434
+ # Removed legacy variables in 3.0.0
1435
+ "DEFAULT_REGION", # deprecated since 0.12.7
1436
+ "EDGE_BIND_HOST", # deprecated since 2.0.0
1437
+ "EDGE_FORWARD_URL", # deprecated since 1.4.0
1438
+ "EDGE_PORT", # deprecated since 2.0.0
1439
+ "EDGE_PORT_HTTP", # deprecated since 2.0.0
1440
+ "ES_CUSTOM_BACKEND", # deprecated since 0.14.0
1441
+ "ES_ENDPOINT_STRATEGY", # deprecated since 0.14.0
1442
+ "ES_MULTI_CLUSTER", # deprecated since 0.14.0
1443
+ "HOSTNAME_EXTERNAL", # deprecated since 2.0.0
1444
+ "KINESIS_INITIALIZE_STREAMS", # deprecated since 1.4.0
1445
+ "KINESIS_PROVIDER", # deprecated since 1.3.0
1446
+ "KMS_PROVIDER", # deprecated since 1.4.0
1447
+ "LAMBDA_XRAY_INIT", # deprecated since 2.0.0
1448
+ "LAMBDA_CODE_EXTRACT_TIME", # deprecated since 2.0.0
1449
+ "LAMBDA_CONTAINER_REGISTRY", # deprecated since 2.0.0
1450
+ "LAMBDA_EXECUTOR", # deprecated since 2.0.0
1451
+ "LAMBDA_FALLBACK_URL", # deprecated since 2.0.0
1452
+ "LAMBDA_FORWARD_URL", # deprecated since 2.0.0
1453
+ "LAMBDA_JAVA_OPTS", # currently only supported in old Lambda provider but not officially deprecated
1454
+ "LAMBDA_REMOTE_DOCKER", # deprecated since 2.0.0
1455
+ "LAMBDA_STAY_OPEN_MODE", # deprecated since 2.0.0
1456
+ "LEGACY_EDGE_PROXY", # deprecated since 1.0.0
1457
+ "LOCALSTACK_HOSTNAME", # deprecated since 2.0.0
1458
+ "SQS_PORT_EXTERNAL", # deprecated only in docs since 2022-07-13
1459
+ "SYNCHRONOUS_KINESIS_EVENTS", # deprecated since 1.3.0
1460
+ "USE_SINGLE_REGION", # deprecated since 0.12.7
1461
+ "MOCK_UNIMPLEMENTED", # deprecated since 1.3.0
1462
+ ]
1463
+
1464
+
1465
+ def is_local_test_mode() -> bool:
1466
+ """Returns True if we are running in the context of our local integration tests."""
1467
+ return is_env_true(ENV_INTERNAL_TEST_RUN)
1468
+
1469
+
1470
+ def is_collect_metrics_mode() -> bool:
1471
+ """Returns True if metric collection is enabled."""
1472
+ return is_env_true(ENV_INTERNAL_TEST_COLLECT_METRIC)
1473
+
1474
+
1475
+ def store_test_metrics_in_local_filesystem() -> bool:
1476
+ """Returns True if test metrics should be stored in the local filesystem (instead of the system that runs pytest)."""
1477
+ return is_env_true(ENV_INTERNAL_TEST_STORE_METRICS_IN_LOCALSTACK)
1478
+
1479
+
1480
+ def collect_config_items() -> list[tuple[str, Any]]:
1481
+ """Returns a list of key-value tuples of LocalStack configuration values."""
1482
+ none = object() # sentinel object
1483
+
1484
+ # collect which keys to print
1485
+ keys = []
1486
+ keys.extend(CONFIG_ENV_VARS)
1487
+ keys.append("DATA_DIR")
1488
+ keys.sort()
1489
+
1490
+ values = globals()
1491
+
1492
+ result = []
1493
+ for k in keys:
1494
+ v = values.get(k, none)
1495
+ if v is none:
1496
+ continue
1497
+ result.append((k, v))
1498
+ result.sort()
1499
+ return result
1500
+
1501
+
1502
+ def populate_config_env_var_names():
1503
+ global CONFIG_ENV_VARS
1504
+
1505
+ CONFIG_ENV_VARS += [
1506
+ key
1507
+ for key in [key.upper() for key in os.environ]
1508
+ if (key.startswith("LOCALSTACK_") or key.startswith("PROVIDER_OVERRIDE_"))
1509
+ # explicitly exclude LOCALSTACK_CLI (it's prefixed with "LOCALSTACK_",
1510
+ # but is only used in the CLI (should not be forwarded to the container)
1511
+ and key != "LOCALSTACK_CLI"
1512
+ ]
1513
+
1514
+ # create variable aliases prefixed with LOCALSTACK_ (except LOCALSTACK_HOST)
1515
+ CONFIG_ENV_VARS += [
1516
+ "LOCALSTACK_" + v for v in CONFIG_ENV_VARS if not v.startswith("LOCALSTACK_")
1517
+ ]
1518
+
1519
+ CONFIG_ENV_VARS = list(set(CONFIG_ENV_VARS))
1520
+
1521
+
1522
+ # populate env var names to be passed to the container
1523
+ populate_config_env_var_names()
1524
+
1525
+
1526
+ # helpers to build urls
1527
+ def get_protocol() -> str:
1528
+ return "https" if USE_SSL else "http"
1529
+
1530
+
1531
+ def external_service_url(
1532
+ host: str | None = None,
1533
+ port: int | None = None,
1534
+ protocol: str | None = None,
1535
+ subdomains: str | None = None,
1536
+ ) -> str:
1537
+ """Returns a service URL (e.g., SQS queue URL) to an external client (e.g., boto3) potentially running on another
1538
+ machine than LocalStack. The configurations LOCALSTACK_HOST and USE_SSL can customize these returned URLs.
1539
+ The optional parameters can be used to customize the defaults.
1540
+ Examples with default configuration:
1541
+ * external_service_url() == http://localhost.localstack.cloud:4566
1542
+ * external_service_url(subdomains="s3") == http://s3.localhost.localstack.cloud:4566
1543
+ """
1544
+ protocol = protocol or get_protocol()
1545
+ subdomains = f"{subdomains}." if subdomains else ""
1546
+ host = host or LOCALSTACK_HOST.host
1547
+ port = port or LOCALSTACK_HOST.port
1548
+ return f"{protocol}://{subdomains}{host}:{port}"
1549
+
1550
+
1551
+ def internal_service_url(
1552
+ host: str | None = None,
1553
+ port: int | None = None,
1554
+ protocol: str | None = None,
1555
+ subdomains: str | None = None,
1556
+ ) -> str:
1557
+ """Returns a service URL for internal use within LocalStack (i.e., same host).
1558
+ The configuration USE_SSL can customize these returned URLs but LOCALSTACK_HOST has no effect.
1559
+ The optional parameters can be used to customize the defaults.
1560
+ Examples with default configuration:
1561
+ * internal_service_url() == http://localhost:4566
1562
+ * internal_service_url(port=8080) == http://localhost:8080
1563
+ """
1564
+ protocol = protocol or get_protocol()
1565
+ subdomains = f"{subdomains}." if subdomains else ""
1566
+ host = host or LOCALHOST
1567
+ port = port or GATEWAY_LISTEN[0].port
1568
+ return f"{protocol}://{subdomains}{host}:{port}"
1569
+
1570
+
1571
+ # DEPRECATED: old helpers for building URLs
1572
+
1573
+
1574
+ def service_url(service_key, host=None, port=None):
1575
+ """@deprecated: Use `internal_service_url()` instead. We assume that most usages are internal
1576
+ but really need to check and update each usage accordingly.
1577
+ """
1578
+ warnings.warn(
1579
+ """@deprecated: Use `internal_service_url()` instead. We assume that most usages are
1580
+ internal but really need to check and update each usage accordingly.""",
1581
+ DeprecationWarning,
1582
+ stacklevel=2,
1583
+ )
1584
+ return internal_service_url(host=host, port=port)
1585
+
1586
+
1587
+ def service_port(service_key: str, external: bool = False) -> int:
1588
+ """@deprecated: Use `localstack_host().port` for external and `GATEWAY_LISTEN[0].port` for
1589
+ internal use."""
1590
+ warnings.warn(
1591
+ "Deprecated: use `localstack_host().port` for external and `GATEWAY_LISTEN[0].port` for "
1592
+ "internal use.",
1593
+ DeprecationWarning,
1594
+ stacklevel=2,
1595
+ )
1596
+ if external:
1597
+ return LOCALSTACK_HOST.port
1598
+ return GATEWAY_LISTEN[0].port
1599
+
1600
+
1601
+ def get_edge_port_http():
1602
+ """@deprecated: Use `localstack_host().port` for external and `GATEWAY_LISTEN[0].port` for
1603
+ internal use. This function is not needed anymore because we don't separate between HTTP
1604
+ and HTTP ports anymore since LocalStack listens to both ports."""
1605
+ warnings.warn(
1606
+ """@deprecated: Use `localstack_host().port` for external and `GATEWAY_LISTEN[0].port`
1607
+ for internal use. This function is also not needed anymore because we don't separate
1608
+ between HTTP and HTTP ports anymore since LocalStack listens to both.""",
1609
+ DeprecationWarning,
1610
+ stacklevel=2,
1611
+ )
1612
+ return GATEWAY_LISTEN[0].port
1613
+
1614
+
1615
+ def get_edge_url(localstack_hostname=None, protocol=None):
1616
+ """@deprecated: Use `internal_service_url()` instead.
1617
+ We assume that most usages are internal but really need to check and update each usage accordingly.
1618
+ """
1619
+ warnings.warn(
1620
+ """@deprecated: Use `internal_service_url()` instead.
1621
+ We assume that most usages are internal but really need to check and update each usage accordingly.
1622
+ """,
1623
+ DeprecationWarning,
1624
+ stacklevel=2,
1625
+ )
1626
+ return internal_service_url(host=localstack_hostname, protocol=protocol)
1627
+
1628
+
1629
+ class ServiceProviderConfig(Mapping[str, str]):
1630
+ _provider_config: dict[str, str]
1631
+ default_value: str
1632
+ override_prefix: str = "PROVIDER_OVERRIDE_"
1633
+
1634
+ def __init__(self, default_value: str):
1635
+ self._provider_config = {}
1636
+ self.default_value = default_value
1637
+
1638
+ def load_from_environment(self, env: Mapping[str, str] = None):
1639
+ if env is None:
1640
+ env = os.environ
1641
+ for key, value in env.items():
1642
+ if key.startswith(self.override_prefix) and value:
1643
+ self.set_provider(key[len(self.override_prefix) :].lower().replace("_", "-"), value)
1644
+
1645
+ def get_provider(self, service: str) -> str:
1646
+ return self._provider_config.get(service, self.default_value)
1647
+
1648
+ def set_provider_if_not_exists(self, service: str, provider: str) -> None:
1649
+ if service not in self._provider_config:
1650
+ self._provider_config[service] = provider
1651
+
1652
+ def set_provider(self, service: str, provider: str):
1653
+ self._provider_config[service] = provider
1654
+
1655
+ def bulk_set_provider_if_not_exists(self, services: list[str], provider: str):
1656
+ for service in services:
1657
+ self.set_provider_if_not_exists(service, provider)
1658
+
1659
+ def __getitem__(self, item):
1660
+ return self.get_provider(item)
1661
+
1662
+ def __setitem__(self, key, value):
1663
+ self.set_provider(key, value)
1664
+
1665
+ def __len__(self):
1666
+ return len(self._provider_config)
1667
+
1668
+ def __iter__(self):
1669
+ return self._provider_config.__iter__()
1670
+
1671
+
1672
+ SERVICE_PROVIDER_CONFIG = ServiceProviderConfig("default")
1673
+
1674
+ SERVICE_PROVIDER_CONFIG.load_from_environment()
1675
+
1676
+
1677
+ def init_directories() -> Directories:
1678
+ if is_in_docker:
1679
+ return Directories.for_container()
1680
+ else:
1681
+ if is_env_true("LOCALSTACK_CLI"):
1682
+ return Directories.for_cli()
1683
+
1684
+ return Directories.for_host()
1685
+
1686
+
1687
+ # initialize directories
1688
+ dirs: Directories
1689
+ dirs = init_directories()