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,90 @@
1
+ import os.path
2
+
3
+ import click
4
+ from localstack_cli.pro.core.bootstrap.licensingv2 import (
5
+ AuthToken,
6
+ DevLocalstackEnvironment,
7
+ LicensingError,
8
+ get_credentials_from_environment,
9
+ get_licensed_environment,
10
+ )
11
+
12
+
13
+ @click.group(
14
+ name="license",
15
+ short_help="(Preview) Manage and verify your LocalStack license",
16
+ help="""
17
+ (Preview) Manage and verify your LocalStack license.
18
+
19
+ Your LocalStack license allows you to use advanced features of LocalStack.
20
+ """,
21
+ )
22
+ def license() -> None:
23
+ pass
24
+
25
+
26
+ @license.command("info")
27
+ def cmd_info():
28
+ try:
29
+ credentials = get_credentials_from_environment()
30
+ except Exception as e:
31
+ click.echo(f"credentials: {e}")
32
+ return
33
+
34
+ if credentials:
35
+ if isinstance(credentials, AuthToken):
36
+ if credentials.encoded() == "test":
37
+ credential_validity = "test"
38
+ else:
39
+ credential_validity = "valid syntax" if credentials.is_valid() else "invalid"
40
+ else:
41
+ credential_validity = ""
42
+
43
+ click.echo(
44
+ f"credentials: {type(credentials).__name__}({credentials.encoded()}) {credential_validity}"
45
+ )
46
+ else:
47
+ click.echo("credentials: none found in environment")
48
+
49
+ env = get_licensed_environment()
50
+ if isinstance(env, DevLocalstackEnvironment):
51
+ click.echo("test credentials used, using unlicensed dev environment")
52
+ return
53
+
54
+ license_file_path = None
55
+ for path in env.get_license_file_read_locations():
56
+ if os.path.isfile(path):
57
+ license_file_path = path
58
+ break
59
+
60
+ license_ = None
61
+ if license_file_path:
62
+ try:
63
+ with open(license_file_path, "rb") as fd:
64
+ content = fd.read()
65
+ license_ = env.parser.parse(content)
66
+ text_content = content.decode("utf-8")
67
+ click.echo(f"license location: {license_file_path}")
68
+ click.echo(f"license: {text_content}")
69
+ except LicensingError as e:
70
+ click.echo(f"license location: {license_file_path}")
71
+ click.echo(f"license: error reading license file {e}")
72
+
73
+ if license_:
74
+ try:
75
+ env.client.validate_license(env.require_valid_credentials(), license_)
76
+ click.echo("license validity: valid")
77
+ except LicensingError as e:
78
+ click.echo(f"license validity: invalid ({e})")
79
+
80
+
81
+ @license.command("activate")
82
+ def cmd_activate():
83
+ try:
84
+ env = get_licensed_environment()
85
+ env.activate()
86
+ text_license = env.serializer.serialize(env.license).decode("utf-8")
87
+ click.echo("license activation completed")
88
+ click.echo(f"license: {text_license}")
89
+ except LicensingError as e:
90
+ click.echo(e.get_user_friendly())
@@ -0,0 +1,118 @@
1
+ import logging
2
+ from typing import Any
3
+
4
+ import click
5
+ from localstack_cli.cli import LocalstackCli, LocalstackCliPlugin, console
6
+ from localstack_cli.utils.analytics.cli import publish_invocation
7
+
8
+ from .auth import auth
9
+ from .aws import aws
10
+ from .cli import RequiresLicenseGroup
11
+ from .cloud_pods import pod
12
+ from .ephemeral import ephemeral
13
+ from .extensions import extensions
14
+ from .iam import iam # noqa
15
+ from .license import license
16
+ from .replicator import replicator
17
+ from .state import state
18
+
19
+
20
+ class ProCliPlugins(LocalstackCliPlugin):
21
+ """Plugin that adds more CLI commands to the localstack CLI. Some commands are subject to a license
22
+ activation, but all of them are shown by default."""
23
+
24
+ name = "pro"
25
+
26
+ def attach(self, cli: LocalstackCli) -> None:
27
+ group: click.Group = cli.group
28
+ group.add_command(dns)
29
+ group.add_command(aws) # noqa
30
+ group.add_command(extensions) # noqa
31
+ group.add_command(license) # noqa
32
+ group.add_command(state) # noqa
33
+ group.add_command(auth) # noqa
34
+ group.add_command(pod) # noqa
35
+ group.add_command(ephemeral) # noqa
36
+ group.add_command(replicator) # noqa
37
+
38
+
39
+ @click.group(
40
+ name="dns",
41
+ short_help="Manage LocalStack DNS host config",
42
+ help="""
43
+ Manage the usage of the LocalStack DNS on your host.
44
+
45
+ This command provides tools to configure your the DNS on your host machine to use the LocalStack DNS
46
+ on your host machine.
47
+ The LocalStack DNS is used for certain Pro features (like the transparent endpoint injection).
48
+
49
+ \b
50
+ Visit https://docs.localstack.cloud/user-guide/tools/transparent-endpoint-injection/dns-server/
51
+ for more information on the LocalStack DNS and how it is used.
52
+ """,
53
+ cls=RequiresLicenseGroup,
54
+ )
55
+ def dns() -> None:
56
+ pass
57
+
58
+
59
+ @dns.command(
60
+ name="systemd-resolved",
61
+ short_help="Manage LocalStack DNS in systemd-resolved",
62
+ help="""
63
+ Manage the LocalStack DNS configuration using systemd-resolved (Ubuntu, Debian, etc.).
64
+
65
+ This command sets (or reverts) the LocalStack DNS, running in the current LocalStack runtime, in
66
+ systemd-resolved for the docker network interface.
67
+ Most current Linux systems - like Ubuntu, Debian, or Fedora - use systemd-resolved for the network name
68
+ resolution.
69
+ """,
70
+ )
71
+ @click.option("--set/--revert", "-s/-r", "set_", default=True, help="Set or revert DNS settings")
72
+ @publish_invocation
73
+ def cmd_dns_systemd(set_: bool) -> None:
74
+ import localstack_cli.pro.core.bootstrap.dns_utils
75
+ from localstack_cli.pro.core.bootstrap.dns_utils import configure_systemd
76
+
77
+ console.print("Configuring systemd-resolved...")
78
+ logger_name = localstack_cli.pro.core.bootstrap.dns_utils.LOG.name
79
+ localstack_cli.pro.core.bootstrap.dns_utils.LOG = ConsoleLogger(logger_name)
80
+ configure_systemd(not set_)
81
+
82
+
83
+ class ConsoleLogger(logging.Logger):
84
+ def __init__(self, name):
85
+ super().__init__(name)
86
+
87
+ def info(
88
+ self,
89
+ msg: Any,
90
+ *args: Any,
91
+ **kwargs: Any,
92
+ ) -> None:
93
+ console.print(msg % args)
94
+
95
+ def warning(
96
+ self,
97
+ msg: Any,
98
+ *args: Any,
99
+ **kwargs: Any,
100
+ ) -> None:
101
+ console.print("[red]Warning:[/red] ", msg % args)
102
+
103
+ def error(
104
+ self,
105
+ msg: Any,
106
+ *args: Any,
107
+ **kwargs: Any,
108
+ ) -> None:
109
+ console.print("[red]Error:[/red] ", msg % args)
110
+
111
+ def exception(
112
+ self,
113
+ msg: Any,
114
+ *args: Any,
115
+ **kwargs: Any,
116
+ ) -> None:
117
+ console.print("[red]Error:[/red] ", msg % args)
118
+ console.print_exception()
@@ -0,0 +1,378 @@
1
+ import json
2
+ import os
3
+ import re
4
+ import subprocess as sp
5
+ import sys
6
+ import time
7
+ from collections.abc import Mapping
8
+ from configparser import ConfigParser
9
+ from pathlib import Path
10
+ from typing import TypedDict
11
+
12
+ import click
13
+ import requests
14
+
15
+ from localstack_cli import config
16
+ from localstack_cli.cli import console
17
+ from localstack_cli.cli.exceptions import CLIError
18
+
19
+ from .cli import RequiresPlatformLicenseGroup, _assert_host_reachable
20
+
21
+ AWS_CONFIG_ENV_VARS = {
22
+ "aws_access_key_id": "{}_ACCESS_KEY_ID",
23
+ "aws_secret_access_key": "{}_SECRET_ACCESS_KEY",
24
+ "aws_session_token": "{}_SESSION_TOKEN",
25
+ "region_name": "{}_DEFAULT_REGION",
26
+ "endpoint_url": "{}_ENDPOINT_URL",
27
+ "profile_name": "{}_PROFILE",
28
+ }
29
+
30
+ PREVIEW_BANNER = """
31
+ *** Preview Feature ***
32
+
33
+ This feature is currently in preview mode in our Teams offering and it's availability may change in future releases.
34
+ """
35
+
36
+ REPLICATOR_HELP = (
37
+ PREVIEW_BANNER
38
+ + """
39
+
40
+ The replicator command group allows you to replicate AWS resources into LocalStack.
41
+ """
42
+ )
43
+
44
+
45
+ def run_check_output(cmd) -> str:
46
+ return sp.check_output(cmd, stderr=sp.PIPE, env=os.environ).decode("utf-8").strip()
47
+
48
+
49
+ class ProfileLoadError(RuntimeError):
50
+ def __init__(self, profile_name: str):
51
+ super().__init__(f"Could not find profile '{profile_name}'")
52
+
53
+
54
+ class ReplicatorCliGroup(RequiresPlatformLicenseGroup):
55
+ name = "replicator"
56
+ tier = "Ultimate"
57
+
58
+ def invoke(self, ctx: click.Context):
59
+ print(PREVIEW_BANNER, file=sys.stderr)
60
+ super().invoke(ctx)
61
+
62
+
63
+ class AWSConfig(TypedDict, total=False):
64
+ aws_access_key_id: str
65
+ aws_secret_access_key: str
66
+ aws_session_token: str | None
67
+ region_name: str
68
+ endpoint_url: str | None
69
+ profile_name: str
70
+
71
+
72
+ def get_aws_env_config(prefix: str) -> AWSConfig:
73
+ aws_config = {
74
+ key: os.getenv(value.format(prefix)) for key, value in AWS_CONFIG_ENV_VARS.items()
75
+ }
76
+ return AWSConfig(**{k: v for k, v in aws_config.items() if v})
77
+
78
+
79
+ def get_config_from_profile(profile_name: str, profile_dir: Path | None = None) -> AWSConfig:
80
+ profile_dir = profile_dir or Path.home() / ".aws"
81
+
82
+ def load_config_for_profile(path: Path, profile_prefix: str = "") -> Mapping:
83
+ parser = ConfigParser()
84
+ parser.read(path)
85
+ try:
86
+ return parser[f"{profile_prefix}{profile_name}"]
87
+ except KeyError:
88
+ raise ProfileLoadError(profile_name)
89
+
90
+ config = load_config_for_profile(profile_dir / "config", profile_prefix="profile ")
91
+ credentials = load_config_for_profile(profile_dir / "credentials")
92
+
93
+ return AWSConfig(
94
+ aws_access_key_id=credentials["aws_access_key_id"],
95
+ aws_secret_access_key=credentials["aws_secret_access_key"],
96
+ region_name=config["region"],
97
+ profile_name=profile_name,
98
+ )
99
+
100
+
101
+ def get_awscli_config() -> AWSConfig | None:
102
+ try:
103
+ # get credentials values
104
+ cmd = ["aws", "configure", "export-credentials"]
105
+ credentials = json.loads(run_check_output(cmd))
106
+ except sp.CalledProcessError as exc:
107
+ if b"AWS CLI version 2" in exc.stderr:
108
+ print(
109
+ "Warning: awscli v1 installed. Please use v2 for auto detection of credentials",
110
+ file=sys.stderr,
111
+ )
112
+ return None
113
+
114
+ try:
115
+ # try to get the endpoint url
116
+ cmd = ["aws", "configure", "get", "endpoint_url"]
117
+ endpoint_url = run_check_output(cmd)
118
+ except sp.CalledProcessError:
119
+ # If there are no endpoint configured an exception is raised we do a last
120
+ # check in the environment to find the endpoint url
121
+ endpoint_url = os.getenv("AWS_ENDPOINT_URL")
122
+
123
+ try:
124
+ # try to get the region from configure
125
+ cmd = ["aws", "configure", "get", "region"]
126
+ region_name = run_check_output(cmd)
127
+ except sp.CalledProcessError:
128
+ # If there are no default configured an exception is raised we do a last
129
+ # check in the environment to find the region
130
+ region_name = os.getenv("AWS_DEFAULT_REGION")
131
+
132
+ if not region_name:
133
+ try:
134
+ # older awscli versions do not return the region to the command `aws configure get region`.
135
+ # We need to rely on the configure list in this case
136
+ cmd = ["aws", "configure", "list"]
137
+ for line in run_check_output(cmd).splitlines():
138
+ if "region" not in line:
139
+ continue
140
+ # aws changed the format of configure and added `:` in delimiters
141
+ words = re.split(r"[:\s]+", line)
142
+ try:
143
+ region_name = words[1]
144
+ break
145
+ except IndexError:
146
+ return None
147
+ except (sp.CalledProcessError, FileNotFoundError):
148
+ return None
149
+
150
+ return AWSConfig(
151
+ aws_access_key_id=credentials["AccessKeyId"],
152
+ aws_secret_access_key=credentials["SecretAccessKey"],
153
+ aws_session_token=credentials.get("SessionToken"),
154
+ region_name=region_name,
155
+ endpoint_url=endpoint_url,
156
+ )
157
+
158
+
159
+ def get_source_config(profile_dir: Path | None = None) -> AWSConfig:
160
+ awscli_source_config = get_awscli_config()
161
+ if awscli_source_config:
162
+ print("Configured credentials from the AWS CLI", file=sys.stderr)
163
+ return awscli_source_config
164
+
165
+ source_config = get_aws_env_config("AWS")
166
+ profile_name = source_config.get("profile_name")
167
+
168
+ if source_config.get("aws_access_key_id") and not source_config.get("aws_secret_access_key"):
169
+ raise CLIError(
170
+ "Unable to retrieve credentials: Partial credentials found in env."
171
+ " Need both 'AWS_ACCESS_KEY_ID' and 'AWS_SECRET_ACCESS_KEY'"
172
+ )
173
+
174
+ if profile_name:
175
+ config_file_source_config = get_config_from_profile(
176
+ profile_name=profile_name, profile_dir=profile_dir
177
+ )
178
+ if source_config.get("aws_secret_access_key"):
179
+ # before merging, if the secret access key is in the env vars we need to ensure the session token from the
180
+ # config file isn't carried along
181
+ config_file_source_config.pop("aws_session_token", None)
182
+ config_file_source_config.update(source_config)
183
+ source_config = config_file_source_config
184
+
185
+ errors = []
186
+
187
+ if not source_config.get("region_name"):
188
+ errors.append("'AWS_DEFAULT_REGION' must bet set in environment or in profile.")
189
+ if not source_config.get("aws_access_key_id"):
190
+ errors.append("'AWS_ACCESS_KEY_ID' must bet set in environment or in profile.")
191
+ if not source_config.get("aws_secret_access_key"):
192
+ errors.append("'AWS_SECRET_ACCESS_KEY' must bet set in environment or in profile.")
193
+
194
+ if errors:
195
+ raise CLIError("\n".join(errors))
196
+
197
+ return source_config
198
+
199
+
200
+ def get_target_config(access_key: str = "", region_name: str = "") -> AWSConfig:
201
+ target_config = get_aws_env_config("TARGET")
202
+ if access_key:
203
+ target_config["aws_access_key_id"] = access_key
204
+ if region_name:
205
+ target_config["region_name"] = region_name
206
+
207
+ return target_config
208
+
209
+
210
+ def get_replicator_url():
211
+ _assert_host_reachable()
212
+ return f"{config.external_service_url()}/_localstack/replicator"
213
+
214
+
215
+ @click.group(
216
+ name="replicator",
217
+ short_help="(Preview) Start a replication job or check its status",
218
+ help=REPLICATOR_HELP,
219
+ cls=ReplicatorCliGroup,
220
+ )
221
+ def replicator() -> None:
222
+ pass
223
+
224
+
225
+ def validate_start_command(
226
+ replication_type: str,
227
+ resource_arn: str | None = None,
228
+ resource_type: str | None = None,
229
+ resource_identifier: str | None = None,
230
+ ) -> None:
231
+ if replication_type == "SINGLE_RESOURCE":
232
+ if not (resource_arn or resource_type):
233
+ raise CLIError("You must specify either --resource-arn or --resource_type")
234
+
235
+ if resource_arn and resource_type:
236
+ raise CLIError("You must specify either --resource-arn or --resource_type")
237
+
238
+ if resource_type and not resource_identifier:
239
+ raise CLIError("You must specify --resource-id when using --resource-type")
240
+
241
+ if resource_arn and not resource_arn.startswith("arn:aws:"):
242
+ raise CLIError("--resource-arn must start with arn:aws:")
243
+
244
+ return None
245
+
246
+
247
+ @replicator.command(
248
+ name="start",
249
+ short_help="Replicate an AWS resource",
250
+ help="""
251
+ Starts a job to replicate an AWS resource into localstack.
252
+ You must have credentials with sufficient read access to the resource trying to replicate.
253
+ At the moment only environment variables are recognized.
254
+ `AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY` and `AWS_DEFAULT_REGION` must be set. `AWS_ENDPOINT_URL` and `AWS_SESSION_TOKEN` are optional.
255
+ """,
256
+ )
257
+ @click.option(
258
+ "--replication-type",
259
+ type=click.Choice(["SINGLE_RESOURCE", "BATCH"]),
260
+ default="SINGLE_RESOURCE",
261
+ show_default=True,
262
+ help="Type of replication job: SINGLE_RESOURCE, BATCH",
263
+ )
264
+ @click.option(
265
+ "--explore-strategy",
266
+ type=click.Choice(["SIMPLE", "TREE"]),
267
+ default="SIMPLE",
268
+ show_default=True,
269
+ help="How we explore the resource tree. SIMPLE only replicates the resource requested.",
270
+ )
271
+ @click.option(
272
+ "--resource-arn",
273
+ help="ARN of the resource to recreate. Optional for SINGLE_RESOURCE replication",
274
+ )
275
+ @click.option(
276
+ "--resource-type",
277
+ help="CloudControl type of the resource to recreate. Optional for SINGLE_RESOURCE replication",
278
+ )
279
+ @click.option(
280
+ "--resource-identifier",
281
+ help="CloudControl identifier of the resource to recreate. Mandatory if --resource-type is used",
282
+ )
283
+ @click.option(
284
+ "--target-account-id",
285
+ # TODO add link to the docs
286
+ help="Localstack account ID where the resources will be replicated. Defaults to 000000000000. See <docs> to enable same account replication",
287
+ )
288
+ @click.option(
289
+ "--target-region-name",
290
+ # TODO add link to the docs
291
+ help="Localstack region where the resources will be replicated. Only provide if different than source AWS account.",
292
+ )
293
+ @click.option("--delay", help="Delay for the MOCK replication work")
294
+ def start(
295
+ replication_type: str,
296
+ explore_strategy: str,
297
+ resource_arn: str | None = None,
298
+ resource_type: str | None = None,
299
+ resource_identifier: str | None = None,
300
+ delay: str | None = None,
301
+ target_account_id: str | None = None,
302
+ target_region_name: str | None = None,
303
+ ) -> None:
304
+ validate_start_command(replication_type, resource_arn, resource_type, resource_identifier)
305
+
306
+ source_config = get_source_config()
307
+ # TODO Do we want to keep those in the cli? there may be some Most Common Settings
308
+ # that we can allow from the cli for simplicity, and the rest through env var?
309
+ target_config = get_target_config(access_key=target_account_id, region_name=target_region_name)
310
+
311
+ replication_config = {}
312
+ if resource_arn:
313
+ replication_config["resource_arn"] = resource_arn
314
+ if resource_type:
315
+ replication_config["resource_type"] = resource_type
316
+ if resource_identifier:
317
+ replication_config["resource_identifier"] = resource_identifier
318
+
319
+ if replication_type == "MOCK":
320
+ replication_config["delay"] = float(delay) if delay else 1
321
+
322
+ replicator_url = f"{get_replicator_url()}/jobs"
323
+
324
+ payload = {
325
+ "replication_type": replication_type,
326
+ "explore_strategy": explore_strategy,
327
+ "replication_job_config": replication_config,
328
+ "source_aws_config": source_config,
329
+ "target_aws_config": target_config,
330
+ }
331
+ response = requests.post(replicator_url, json=payload)
332
+
333
+ if response.status_code == 200:
334
+ console.print_json(json=response.text)
335
+ else:
336
+ raise CLIError(f"Failed to create replication job: {response.status_code}, {response.text}")
337
+
338
+
339
+ @replicator.command(
340
+ name="status",
341
+ short_help="Check replication status",
342
+ help="""
343
+ Check the status of a replication job using its Job ID.
344
+ Use the --follow flag to continuously check the status until the job is completed.
345
+ """,
346
+ )
347
+ @click.argument("job_id")
348
+ @click.option("--follow", is_flag=True, help="Follow the status until completed")
349
+ @click.option("--delay", help="Delay between calls", default=5, type=int)
350
+ def status(job_id, follow: bool, delay: int) -> None:
351
+ url = f"{get_replicator_url()}/jobs/{job_id}"
352
+ while True:
353
+ response = requests.get(url)
354
+
355
+ if response.status_code == 200:
356
+ job = response.json()
357
+ console.print_json(data=job)
358
+ job_state = job.get("state")
359
+ if job_state == "ERROR":
360
+ raise CLIError(job.get("error_message"))
361
+ elif job_state == "SUCCEEDED":
362
+ return
363
+ else:
364
+ raise CLIError(f"Failed to replicate resource: {response.status_code}, {response.text}")
365
+
366
+ if not follow:
367
+ return
368
+ time.sleep(float(delay))
369
+
370
+
371
+ @replicator.command(name="resources", short_help="List supported resources")
372
+ def resources():
373
+ url = f"{get_replicator_url()}/resources"
374
+ response = requests.get(url)
375
+ if response.status_code != 200:
376
+ raise CLIError(f"Failed to get list of resources: {response.status_code}, {response.text}")
377
+
378
+ console.print_json(json=response.text)