playground-ls-cli 4.14.1.dev8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- localstack_cli/__init__.py +0 -0
- localstack_cli/cli/__init__.py +10 -0
- localstack_cli/cli/console.py +11 -0
- localstack_cli/cli/core_plugin.py +12 -0
- localstack_cli/cli/exceptions.py +19 -0
- localstack_cli/cli/localstack.py +951 -0
- localstack_cli/cli/lpm.py +138 -0
- localstack_cli/cli/main.py +22 -0
- localstack_cli/cli/plugin.py +39 -0
- localstack_cli/cli/plugins.py +134 -0
- localstack_cli/cli/profiles.py +65 -0
- localstack_cli/config.py +1689 -0
- localstack_cli/constants.py +165 -0
- localstack_cli/logging/__init__.py +0 -0
- localstack_cli/logging/format.py +194 -0
- localstack_cli/logging/setup.py +142 -0
- localstack_cli/packages/__init__.py +25 -0
- localstack_cli/packages/api.py +418 -0
- localstack_cli/packages/core.py +416 -0
- localstack_cli/pro/__init__.py +0 -0
- localstack_cli/pro/core/__init__.py +0 -0
- localstack_cli/pro/core/bootstrap/__init__.py +1 -0
- localstack_cli/pro/core/bootstrap/auth.py +213 -0
- localstack_cli/pro/core/bootstrap/dns_utils.py +55 -0
- localstack_cli/pro/core/bootstrap/entitlements.py +117 -0
- localstack_cli/pro/core/bootstrap/extensions/__init__.py +3 -0
- localstack_cli/pro/core/bootstrap/extensions/__main__.py +106 -0
- localstack_cli/pro/core/bootstrap/extensions/autoinstall.py +63 -0
- localstack_cli/pro/core/bootstrap/extensions/bootstrap.py +97 -0
- localstack_cli/pro/core/bootstrap/extensions/repository.py +374 -0
- localstack_cli/pro/core/bootstrap/licensingv2.py +1259 -0
- localstack_cli/pro/core/bootstrap/pods/__init__.py +0 -0
- localstack_cli/pro/core/bootstrap/pods/api_types.py +17 -0
- localstack_cli/pro/core/bootstrap/pods/constants.py +26 -0
- localstack_cli/pro/core/bootstrap/pods/remotes/__init__.py +0 -0
- localstack_cli/pro/core/bootstrap/pods/remotes/api.py +75 -0
- localstack_cli/pro/core/bootstrap/pods/remotes/configs.py +69 -0
- localstack_cli/pro/core/bootstrap/pods/remotes/params.py +86 -0
- localstack_cli/pro/core/bootstrap/pods_client.py +834 -0
- localstack_cli/pro/core/cli/__init__.py +0 -0
- localstack_cli/pro/core/cli/auth.py +226 -0
- localstack_cli/pro/core/cli/aws.py +16 -0
- localstack_cli/pro/core/cli/cli.py +99 -0
- localstack_cli/pro/core/cli/click_utils.py +21 -0
- localstack_cli/pro/core/cli/cloud_pods.py +465 -0
- localstack_cli/pro/core/cli/diff_view.py +41 -0
- localstack_cli/pro/core/cli/ephemeral.py +199 -0
- localstack_cli/pro/core/cli/extensions.py +492 -0
- localstack_cli/pro/core/cli/iam.py +180 -0
- localstack_cli/pro/core/cli/license.py +90 -0
- localstack_cli/pro/core/cli/localstack.py +118 -0
- localstack_cli/pro/core/cli/replicator.py +378 -0
- localstack_cli/pro/core/cli/state.py +183 -0
- localstack_cli/pro/core/cli/tree_view.py +235 -0
- localstack_cli/pro/core/config.py +556 -0
- localstack_cli/pro/core/constants.py +54 -0
- localstack_cli/pro/core/plugins.py +169 -0
- localstack_cli/runtime/__init__.py +6 -0
- localstack_cli/runtime/exceptions.py +7 -0
- localstack_cli/runtime/hooks.py +73 -0
- localstack_cli/testing/__init__.py +1 -0
- localstack_cli/testing/config.py +4 -0
- localstack_cli/utils/__init__.py +0 -0
- localstack_cli/utils/analytics/__init__.py +12 -0
- localstack_cli/utils/analytics/cli.py +67 -0
- localstack_cli/utils/analytics/client.py +111 -0
- localstack_cli/utils/analytics/events.py +30 -0
- localstack_cli/utils/analytics/logger.py +48 -0
- localstack_cli/utils/analytics/metadata.py +250 -0
- localstack_cli/utils/analytics/publisher.py +160 -0
- localstack_cli/utils/analytics/service_request_aggregator.py +133 -0
- localstack_cli/utils/archives.py +271 -0
- localstack_cli/utils/batching.py +258 -0
- localstack_cli/utils/bootstrap.py +1418 -0
- localstack_cli/utils/checksum.py +313 -0
- localstack_cli/utils/collections.py +554 -0
- localstack_cli/utils/common.py +229 -0
- localstack_cli/utils/container_networking.py +142 -0
- localstack_cli/utils/container_utils/__init__.py +0 -0
- localstack_cli/utils/container_utils/container_client.py +1585 -0
- localstack_cli/utils/container_utils/docker_cmd_client.py +987 -0
- localstack_cli/utils/container_utils/docker_sdk_client.py +1018 -0
- localstack_cli/utils/crypto.py +294 -0
- localstack_cli/utils/docker_utils.py +272 -0
- localstack_cli/utils/files.py +327 -0
- localstack_cli/utils/functions.py +92 -0
- localstack_cli/utils/http.py +326 -0
- localstack_cli/utils/json.py +219 -0
- localstack_cli/utils/net.py +516 -0
- localstack_cli/utils/no_exit_argument_parser.py +19 -0
- localstack_cli/utils/numbers.py +49 -0
- localstack_cli/utils/objects.py +235 -0
- localstack_cli/utils/patch.py +260 -0
- localstack_cli/utils/platform.py +77 -0
- localstack_cli/utils/run.py +514 -0
- localstack_cli/utils/server/__init__.py +0 -0
- localstack_cli/utils/server/tcp_proxy.py +108 -0
- localstack_cli/utils/serving.py +187 -0
- localstack_cli/utils/ssl.py +71 -0
- localstack_cli/utils/strings.py +245 -0
- localstack_cli/utils/sync.py +267 -0
- localstack_cli/utils/threads.py +163 -0
- localstack_cli/utils/time.py +81 -0
- localstack_cli/utils/urls.py +21 -0
- localstack_cli/utils/venv.py +100 -0
- localstack_cli/utils/xml.py +41 -0
- localstack_cli/version.py +34 -0
- playground_ls_cli-4.14.1.dev8.dist-info/METADATA +95 -0
- playground_ls_cli-4.14.1.dev8.dist-info/RECORD +112 -0
- playground_ls_cli-4.14.1.dev8.dist-info/WHEEL +5 -0
- playground_ls_cli-4.14.1.dev8.dist-info/entry_points.txt +17 -0
- playground_ls_cli-4.14.1.dev8.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,951 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import logging
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
import traceback
|
|
6
|
+
from typing import TypedDict
|
|
7
|
+
|
|
8
|
+
import click
|
|
9
|
+
import requests
|
|
10
|
+
|
|
11
|
+
from localstack_cli import config
|
|
12
|
+
from localstack_cli.cli.exceptions import CLIError
|
|
13
|
+
from localstack_cli.constants import VERSION
|
|
14
|
+
from localstack_cli.utils.analytics.cli import publish_invocation
|
|
15
|
+
from localstack_cli.utils.bootstrap import get_container_default_logfile_location
|
|
16
|
+
from localstack_cli.utils.json import CustomEncoder
|
|
17
|
+
|
|
18
|
+
from .console import BANNER, console
|
|
19
|
+
from .plugin import LocalstackCli, load_cli_plugins
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class LocalStackCliGroup(click.Group):
|
|
23
|
+
"""
|
|
24
|
+
A Click group used for the top-level ``localstack`` command group. It implements global exception handling
|
|
25
|
+
by:
|
|
26
|
+
|
|
27
|
+
- Ignoring click exceptions (already handled)
|
|
28
|
+
- Handling common exceptions (like DockerNotAvailable)
|
|
29
|
+
- Wrapping all unexpected exceptions in a ClickException (for a unified error message)
|
|
30
|
+
|
|
31
|
+
It also implements a custom help formatter to build more fine-grained groups.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
# FIXME: find a way to communicate this from the actual command
|
|
35
|
+
advanced_commands = [
|
|
36
|
+
"aws",
|
|
37
|
+
"dns",
|
|
38
|
+
"extensions",
|
|
39
|
+
"license",
|
|
40
|
+
"login",
|
|
41
|
+
"logout",
|
|
42
|
+
"pod",
|
|
43
|
+
"state",
|
|
44
|
+
"ephemeral",
|
|
45
|
+
"replicator",
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
def invoke(self, ctx: click.Context):
|
|
49
|
+
try:
|
|
50
|
+
return super().invoke(ctx)
|
|
51
|
+
except click.exceptions.Exit:
|
|
52
|
+
# raise Exit exceptions unmodified (e.g., raised on --help)
|
|
53
|
+
raise
|
|
54
|
+
except click.ClickException:
|
|
55
|
+
# don't handle ClickExceptions, just reraise
|
|
56
|
+
if ctx and ctx.params.get("debug"):
|
|
57
|
+
click.echo(traceback.format_exc())
|
|
58
|
+
raise
|
|
59
|
+
except Exception as e:
|
|
60
|
+
if ctx and ctx.params.get("debug"):
|
|
61
|
+
click.echo(traceback.format_exc())
|
|
62
|
+
from localstack_cli.utils.container_utils.container_client import (
|
|
63
|
+
ContainerException,
|
|
64
|
+
DockerNotAvailable,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
if isinstance(e, DockerNotAvailable):
|
|
68
|
+
raise CLIError(
|
|
69
|
+
"Docker could not be found on the system.\n"
|
|
70
|
+
"Please make sure that you have a working docker environment on your machine."
|
|
71
|
+
)
|
|
72
|
+
elif isinstance(e, ContainerException):
|
|
73
|
+
raise CLIError(e.message)
|
|
74
|
+
else:
|
|
75
|
+
# If we have a generic exception, we wrap it in a ClickException
|
|
76
|
+
raise CLIError(str(e)) from e
|
|
77
|
+
|
|
78
|
+
def format_commands(self, ctx: click.Context, formatter: click.HelpFormatter) -> None:
|
|
79
|
+
"""Extra format methods for multi methods that adds all the commands after the options. It also
|
|
80
|
+
groups commands into command categories."""
|
|
81
|
+
categories = {"Commands": [], "Advanced": [], "Deprecated": []}
|
|
82
|
+
|
|
83
|
+
commands = []
|
|
84
|
+
for subcommand in self.list_commands(ctx):
|
|
85
|
+
cmd = self.get_command(ctx, subcommand)
|
|
86
|
+
# What is this, the tool lied about a command. Ignore it
|
|
87
|
+
if cmd is None:
|
|
88
|
+
continue
|
|
89
|
+
if cmd.hidden:
|
|
90
|
+
continue
|
|
91
|
+
|
|
92
|
+
commands.append((subcommand, cmd))
|
|
93
|
+
|
|
94
|
+
# allow for 3 times the default spacing
|
|
95
|
+
if len(commands):
|
|
96
|
+
limit = formatter.width - 6 - max(len(cmd[0]) for cmd in commands)
|
|
97
|
+
|
|
98
|
+
for subcommand, cmd in commands:
|
|
99
|
+
help = cmd.get_short_help_str(limit)
|
|
100
|
+
categories[self._get_category(cmd)].append((subcommand, help))
|
|
101
|
+
|
|
102
|
+
for category, rows in categories.items():
|
|
103
|
+
if rows:
|
|
104
|
+
with formatter.section(category):
|
|
105
|
+
formatter.write_dl(rows)
|
|
106
|
+
|
|
107
|
+
def _get_category(self, cmd) -> str:
|
|
108
|
+
if cmd.deprecated:
|
|
109
|
+
return "Deprecated"
|
|
110
|
+
|
|
111
|
+
if cmd.name in self.advanced_commands:
|
|
112
|
+
return "Advanced"
|
|
113
|
+
|
|
114
|
+
return "Commands"
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def create_with_plugins() -> LocalstackCli:
|
|
118
|
+
"""
|
|
119
|
+
Creates a LocalstackCli instance with all cli plugins loaded.
|
|
120
|
+
:return: a LocalstackCli instance
|
|
121
|
+
"""
|
|
122
|
+
cli = LocalstackCli()
|
|
123
|
+
cli.group = localstack
|
|
124
|
+
load_cli_plugins(cli)
|
|
125
|
+
return cli
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _setup_cli_debug() -> None:
|
|
129
|
+
from localstack_cli.logging.setup import setup_logging_for_cli
|
|
130
|
+
|
|
131
|
+
config.DEBUG = True
|
|
132
|
+
os.environ["DEBUG"] = "1"
|
|
133
|
+
|
|
134
|
+
setup_logging_for_cli(logging.DEBUG if config.DEBUG else logging.INFO)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
# Re-usable format option decorator which can be used across multiple commands
|
|
138
|
+
_click_format_option = click.option(
|
|
139
|
+
"-f",
|
|
140
|
+
"--format",
|
|
141
|
+
"format_",
|
|
142
|
+
type=click.Choice(["table", "plain", "dict", "json"]),
|
|
143
|
+
default="table",
|
|
144
|
+
help="The formatting style for the command output.",
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
@click.group(
|
|
149
|
+
name="localstack",
|
|
150
|
+
help="The LocalStack Command Line Interface (CLI)",
|
|
151
|
+
cls=LocalStackCliGroup,
|
|
152
|
+
context_settings={
|
|
153
|
+
# add "-h" as a synonym for "--help"
|
|
154
|
+
# https://click.palletsprojects.com/en/8.1.x/documentation/#help-parameter-customization
|
|
155
|
+
"help_option_names": ["-h", "--help"],
|
|
156
|
+
# show default values for options by default - https://github.com/pallets/click/pull/1225
|
|
157
|
+
"show_default": True,
|
|
158
|
+
},
|
|
159
|
+
)
|
|
160
|
+
@click.version_option(
|
|
161
|
+
VERSION,
|
|
162
|
+
"--version",
|
|
163
|
+
"-v",
|
|
164
|
+
message="LocalStack CLI %(version)s",
|
|
165
|
+
help="Show the version of the LocalStack CLI and exit",
|
|
166
|
+
)
|
|
167
|
+
@click.option("-d", "--debug", is_flag=True, help="Enable CLI debugging mode")
|
|
168
|
+
@click.option("-p", "--profile", type=str, help="Set the configuration profile")
|
|
169
|
+
def localstack(debug, profile) -> None:
|
|
170
|
+
# --profile is read manually in localstack.cli.main because it needs to be read before localstack.config is read
|
|
171
|
+
|
|
172
|
+
if debug:
|
|
173
|
+
_setup_cli_debug()
|
|
174
|
+
|
|
175
|
+
from localstack_cli.utils.files import cache_dir
|
|
176
|
+
|
|
177
|
+
# overwrite the config variable here to defer import of cache_dir
|
|
178
|
+
if not os.environ.get("LOCALSTACK_VOLUME_DIR", "").strip():
|
|
179
|
+
config.VOLUME_DIR = str(cache_dir() / "volume")
|
|
180
|
+
|
|
181
|
+
# FIXME: at some point we should remove the use of `config.dirs` for the CLI,
|
|
182
|
+
# see https://github.com/localstack/localstack/pull/7906
|
|
183
|
+
config.dirs.for_cli().mkdirs()
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
@localstack.group(
|
|
187
|
+
name="config",
|
|
188
|
+
short_help="Manage your LocalStack config",
|
|
189
|
+
)
|
|
190
|
+
def localstack_config() -> None:
|
|
191
|
+
"""
|
|
192
|
+
Inspect and validate your LocalStack configuration.
|
|
193
|
+
"""
|
|
194
|
+
pass
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
@localstack_config.command(name="show", short_help="Show your config")
|
|
198
|
+
@_click_format_option
|
|
199
|
+
@publish_invocation
|
|
200
|
+
def cmd_config_show(format_: str) -> None:
|
|
201
|
+
"""
|
|
202
|
+
Print the current LocalStack config values.
|
|
203
|
+
|
|
204
|
+
This command prints the LocalStack configuration values from your environment.
|
|
205
|
+
It analyzes the environment variables as well as the LocalStack CLI profile.
|
|
206
|
+
It does _not_ analyze a specific file (like a docker-compose-yml).
|
|
207
|
+
"""
|
|
208
|
+
# TODO: parse values from potential docker-compose file?
|
|
209
|
+
assert config
|
|
210
|
+
|
|
211
|
+
try:
|
|
212
|
+
# only load the pro config if it's available
|
|
213
|
+
from localstack_cli.pro.core import config as pro_config
|
|
214
|
+
|
|
215
|
+
assert pro_config
|
|
216
|
+
except ImportError:
|
|
217
|
+
# the pro package is not available
|
|
218
|
+
return None
|
|
219
|
+
|
|
220
|
+
if format_ == "table":
|
|
221
|
+
_print_config_table()
|
|
222
|
+
elif format_ == "plain":
|
|
223
|
+
_print_config_pairs()
|
|
224
|
+
elif format_ == "dict":
|
|
225
|
+
_print_config_dict()
|
|
226
|
+
elif format_ == "json":
|
|
227
|
+
_print_config_json()
|
|
228
|
+
else:
|
|
229
|
+
_print_config_pairs() # fall back to plain
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
@localstack_config.command(name="validate", short_help="Validate your config")
|
|
233
|
+
@click.option(
|
|
234
|
+
"-f",
|
|
235
|
+
"--file",
|
|
236
|
+
help="Path to compose file",
|
|
237
|
+
default="docker-compose.yml",
|
|
238
|
+
type=click.Path(exists=True, file_okay=True, readable=True),
|
|
239
|
+
)
|
|
240
|
+
@publish_invocation
|
|
241
|
+
def cmd_config_validate(file: str) -> None:
|
|
242
|
+
"""
|
|
243
|
+
Validate your LocalStack configuration (docker compose).
|
|
244
|
+
|
|
245
|
+
This command inspects the given docker-compose file (by default docker-compose.yml in the current working
|
|
246
|
+
directory) and validates if the configuration is valid.
|
|
247
|
+
|
|
248
|
+
\b
|
|
249
|
+
It will show an error and return a non-zero exit code if:
|
|
250
|
+
- The docker-compose file is syntactically incorrect.
|
|
251
|
+
- If the file contains common issues when configuring LocalStack.
|
|
252
|
+
"""
|
|
253
|
+
|
|
254
|
+
from localstack_cli.utils import bootstrap
|
|
255
|
+
|
|
256
|
+
if bootstrap.validate_localstack_config(file):
|
|
257
|
+
console.print("[green]:heavy_check_mark:[/green] config valid")
|
|
258
|
+
sys.exit(0)
|
|
259
|
+
else:
|
|
260
|
+
console.print("[red]:heavy_multiplication_x:[/red] validation error")
|
|
261
|
+
sys.exit(1)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def _print_config_json() -> None:
|
|
265
|
+
import json
|
|
266
|
+
|
|
267
|
+
console.print(json.dumps(dict(config.collect_config_items()), cls=CustomEncoder))
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
def _print_config_pairs() -> None:
|
|
271
|
+
for key, value in config.collect_config_items():
|
|
272
|
+
console.print(f"{key}={value}")
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def _print_config_dict() -> None:
|
|
276
|
+
console.print(dict(config.collect_config_items()))
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def _print_config_table() -> None:
|
|
280
|
+
from rich.table import Table
|
|
281
|
+
|
|
282
|
+
grid = Table(show_header=True)
|
|
283
|
+
grid.add_column("Key")
|
|
284
|
+
grid.add_column("Value")
|
|
285
|
+
|
|
286
|
+
for key, value in config.collect_config_items():
|
|
287
|
+
grid.add_row(key, str(value))
|
|
288
|
+
|
|
289
|
+
console.print(grid)
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
@localstack.group(
|
|
293
|
+
name="status",
|
|
294
|
+
short_help="Query status info",
|
|
295
|
+
invoke_without_command=True,
|
|
296
|
+
)
|
|
297
|
+
@click.pass_context
|
|
298
|
+
def localstack_status(ctx: click.Context) -> None:
|
|
299
|
+
"""
|
|
300
|
+
Query status information about the currently running LocalStack instance.
|
|
301
|
+
"""
|
|
302
|
+
if ctx.invoked_subcommand is None:
|
|
303
|
+
ctx.invoke(localstack_status.get_command(ctx, "docker"))
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
@localstack_status.command(name="docker", short_help="Query LocalStack Docker status")
|
|
307
|
+
@_click_format_option
|
|
308
|
+
def cmd_status_docker(format_: str) -> None:
|
|
309
|
+
"""
|
|
310
|
+
Query information about the currently running LocalStack Docker image, its container,
|
|
311
|
+
and the LocalStack runtime.
|
|
312
|
+
"""
|
|
313
|
+
with console.status("Querying Docker status"):
|
|
314
|
+
_print_docker_status(format_)
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
class DockerStatus(TypedDict, total=False):
|
|
318
|
+
running: bool
|
|
319
|
+
runtime_version: str
|
|
320
|
+
image_tag: str
|
|
321
|
+
image_id: str
|
|
322
|
+
image_created: str
|
|
323
|
+
container_name: str | None
|
|
324
|
+
container_ip: str | None
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def _print_docker_status(format_: str) -> None:
|
|
328
|
+
from localstack_cli.utils import docker_utils
|
|
329
|
+
from localstack_cli.utils.bootstrap import get_docker_image_details, get_server_version
|
|
330
|
+
from localstack_cli.utils.container_networking import get_main_container_ip, get_main_container_name
|
|
331
|
+
|
|
332
|
+
img = get_docker_image_details()
|
|
333
|
+
cont_name = config.MAIN_CONTAINER_NAME
|
|
334
|
+
running = docker_utils.DOCKER_CLIENT.is_container_running(cont_name)
|
|
335
|
+
status = DockerStatus(
|
|
336
|
+
runtime_version=get_server_version(),
|
|
337
|
+
image_tag=img["tag"],
|
|
338
|
+
image_id=img["id"],
|
|
339
|
+
image_created=img["created"],
|
|
340
|
+
running=running,
|
|
341
|
+
)
|
|
342
|
+
if running:
|
|
343
|
+
status["container_name"] = get_main_container_name()
|
|
344
|
+
status["container_ip"] = get_main_container_ip()
|
|
345
|
+
|
|
346
|
+
if format_ == "dict":
|
|
347
|
+
console.print(status)
|
|
348
|
+
if format_ == "table":
|
|
349
|
+
_print_docker_status_table(status)
|
|
350
|
+
if format_ == "json":
|
|
351
|
+
console.print(json.dumps(status))
|
|
352
|
+
if format_ == "plain":
|
|
353
|
+
for key, value in status.items():
|
|
354
|
+
console.print(f"{key}={value}")
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
def _print_docker_status_table(status: DockerStatus) -> None:
|
|
358
|
+
from rich.table import Table
|
|
359
|
+
|
|
360
|
+
grid = Table(show_header=False)
|
|
361
|
+
grid.add_column()
|
|
362
|
+
grid.add_column()
|
|
363
|
+
|
|
364
|
+
grid.add_row("Runtime version", f"[bold]{status['runtime_version']}[/bold]")
|
|
365
|
+
grid.add_row(
|
|
366
|
+
"Docker image",
|
|
367
|
+
f"tag: {status['image_tag']}, "
|
|
368
|
+
f"id: {status['image_id']}, "
|
|
369
|
+
f":calendar: {status['image_created']}",
|
|
370
|
+
)
|
|
371
|
+
cont_status = "[bold][red]:heavy_multiplication_x: stopped"
|
|
372
|
+
if status["running"]:
|
|
373
|
+
cont_status = (
|
|
374
|
+
f"[bold][green]:heavy_check_mark: running[/green][/bold] "
|
|
375
|
+
f'(name: "[italic]{status["container_name"]}[/italic]", IP: {status["container_ip"]})'
|
|
376
|
+
)
|
|
377
|
+
grid.add_row("Runtime status", cont_status)
|
|
378
|
+
console.print(grid)
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
@localstack_status.command(name="services", short_help="Query LocalStack services status")
|
|
382
|
+
@_click_format_option
|
|
383
|
+
def cmd_status_services(format_: str) -> None:
|
|
384
|
+
"""
|
|
385
|
+
Query information about the services of the currently running LocalStack instance.
|
|
386
|
+
"""
|
|
387
|
+
url = config.external_service_url()
|
|
388
|
+
|
|
389
|
+
try:
|
|
390
|
+
health = requests.get(f"{url}/_localstack/health", timeout=2)
|
|
391
|
+
doc = health.json()
|
|
392
|
+
services = doc.get("services", [])
|
|
393
|
+
if format_ == "table":
|
|
394
|
+
_print_service_table(services)
|
|
395
|
+
if format_ == "plain":
|
|
396
|
+
for service, status in services.items():
|
|
397
|
+
console.print(f"{service}={status}")
|
|
398
|
+
if format_ == "dict":
|
|
399
|
+
console.print(services)
|
|
400
|
+
if format_ == "json":
|
|
401
|
+
console.print(json.dumps(services))
|
|
402
|
+
except requests.ConnectionError:
|
|
403
|
+
if config.DEBUG:
|
|
404
|
+
console.print_exception()
|
|
405
|
+
raise CLIError(f"could not connect to LocalStack health endpoint at {url}")
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
def _print_service_table(services: dict[str, str]) -> None:
|
|
409
|
+
from rich.table import Table
|
|
410
|
+
|
|
411
|
+
status_display = {
|
|
412
|
+
"running": "[green]:heavy_check_mark:[/green] running",
|
|
413
|
+
"starting": ":hourglass_flowing_sand: starting",
|
|
414
|
+
"available": "[grey]:heavy_check_mark:[/grey] available",
|
|
415
|
+
"error": "[red]:heavy_multiplication_x:[/red] error",
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
table = Table()
|
|
419
|
+
table.add_column("Service")
|
|
420
|
+
table.add_column("Status")
|
|
421
|
+
|
|
422
|
+
services = list(services.items())
|
|
423
|
+
services.sort(key=lambda item: item[0])
|
|
424
|
+
|
|
425
|
+
for service, status in services:
|
|
426
|
+
if status in status_display:
|
|
427
|
+
status = status_display[status]
|
|
428
|
+
|
|
429
|
+
table.add_row(service, status)
|
|
430
|
+
|
|
431
|
+
console.print(table)
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
@localstack.command(name="start", short_help="Start LocalStack")
|
|
435
|
+
@click.option("--docker", is_flag=True, help="Start LocalStack in a docker container [default]")
|
|
436
|
+
@click.option("--host", is_flag=True, help="Start LocalStack directly on the host", deprecated=True)
|
|
437
|
+
@click.option("--no-banner", is_flag=True, help="Disable LocalStack banner", default=False)
|
|
438
|
+
@click.option(
|
|
439
|
+
"-d", "--detached", is_flag=True, help="Start LocalStack in the background", default=False
|
|
440
|
+
)
|
|
441
|
+
@click.option(
|
|
442
|
+
"--network",
|
|
443
|
+
type=str,
|
|
444
|
+
help="The container network the LocalStack container should be started in. By default, the default docker bridge network is used.",
|
|
445
|
+
required=False,
|
|
446
|
+
)
|
|
447
|
+
@click.option(
|
|
448
|
+
"--env",
|
|
449
|
+
"-e",
|
|
450
|
+
help="Additional environment variables that are passed to the LocalStack container",
|
|
451
|
+
multiple=True,
|
|
452
|
+
required=False,
|
|
453
|
+
)
|
|
454
|
+
@click.option(
|
|
455
|
+
"--publish",
|
|
456
|
+
"-p",
|
|
457
|
+
help="Additional port mappings that are passed to the LocalStack container",
|
|
458
|
+
multiple=True,
|
|
459
|
+
required=False,
|
|
460
|
+
)
|
|
461
|
+
@click.option(
|
|
462
|
+
"--volume",
|
|
463
|
+
"-v",
|
|
464
|
+
help="Additional volume mounts that are passed to the LocalStack container",
|
|
465
|
+
multiple=True,
|
|
466
|
+
required=False,
|
|
467
|
+
)
|
|
468
|
+
@click.option(
|
|
469
|
+
"--host-dns",
|
|
470
|
+
help="Expose the LocalStack DNS server to the host using port bindings.",
|
|
471
|
+
required=False,
|
|
472
|
+
is_flag=True,
|
|
473
|
+
default=False,
|
|
474
|
+
)
|
|
475
|
+
@click.option(
|
|
476
|
+
"--stack",
|
|
477
|
+
"-s",
|
|
478
|
+
type=str,
|
|
479
|
+
help="Use a specific stack with optional version. Examples: [localstack:4.5, snowflake]",
|
|
480
|
+
required=False,
|
|
481
|
+
)
|
|
482
|
+
@publish_invocation
|
|
483
|
+
def cmd_start(
|
|
484
|
+
docker: bool,
|
|
485
|
+
host: bool,
|
|
486
|
+
no_banner: bool,
|
|
487
|
+
detached: bool,
|
|
488
|
+
network: str = None,
|
|
489
|
+
env: tuple = (),
|
|
490
|
+
publish: tuple = (),
|
|
491
|
+
volume: tuple = (),
|
|
492
|
+
host_dns: bool = False,
|
|
493
|
+
stack: str = None,
|
|
494
|
+
) -> None:
|
|
495
|
+
"""
|
|
496
|
+
Start the LocalStack runtime.
|
|
497
|
+
|
|
498
|
+
This command starts the LocalStack runtime with your current configuration.
|
|
499
|
+
By default, it will start a new Docker container from the latest LocalStack(-Pro) Docker image
|
|
500
|
+
with best-practice volume mounts and port mappings.
|
|
501
|
+
"""
|
|
502
|
+
if docker and host:
|
|
503
|
+
raise CLIError("Please specify either --docker or --host")
|
|
504
|
+
if host and detached:
|
|
505
|
+
raise CLIError("Cannot start detached in host mode")
|
|
506
|
+
|
|
507
|
+
if stack:
|
|
508
|
+
# Validate allowed stacks
|
|
509
|
+
stack_name = stack.split(":")[0]
|
|
510
|
+
allowed_stacks = ("localstack", "localstack-pro", "snowflake")
|
|
511
|
+
if stack_name.lower() not in allowed_stacks:
|
|
512
|
+
raise CLIError(f"Invalid stack '{stack_name}'. Allowed stacks: {allowed_stacks}.")
|
|
513
|
+
|
|
514
|
+
# Set IMAGE_NAME, defaulting to :latest if no version specified
|
|
515
|
+
if ":" not in stack:
|
|
516
|
+
stack = f"{stack}:latest"
|
|
517
|
+
os.environ["IMAGE_NAME"] = f"localstack/{stack}"
|
|
518
|
+
|
|
519
|
+
if not no_banner:
|
|
520
|
+
print_banner()
|
|
521
|
+
print_version()
|
|
522
|
+
print_profile()
|
|
523
|
+
print_app()
|
|
524
|
+
console.line()
|
|
525
|
+
|
|
526
|
+
from localstack_cli.utils import bootstrap
|
|
527
|
+
|
|
528
|
+
if not no_banner:
|
|
529
|
+
if host:
|
|
530
|
+
console.log("starting LocalStack in host mode :laptop_computer:")
|
|
531
|
+
else:
|
|
532
|
+
console.log("starting LocalStack in Docker mode :whale:")
|
|
533
|
+
|
|
534
|
+
if host:
|
|
535
|
+
console.log(
|
|
536
|
+
"Warning: Starting LocalStack in host mode from the CLI is deprecated and will be removed soon. Please use the default Docker mode instead.",
|
|
537
|
+
style="bold red",
|
|
538
|
+
)
|
|
539
|
+
|
|
540
|
+
# call hooks to prepare host
|
|
541
|
+
bootstrap.prepare_host(console)
|
|
542
|
+
|
|
543
|
+
# from here we abandon the regular CLI control path and start treating the process like a localstack
|
|
544
|
+
# runtime process
|
|
545
|
+
os.environ["LOCALSTACK_CLI"] = "0"
|
|
546
|
+
config.dirs = config.init_directories()
|
|
547
|
+
|
|
548
|
+
try:
|
|
549
|
+
bootstrap.start_infra_locally()
|
|
550
|
+
except ImportError:
|
|
551
|
+
if config.DEBUG:
|
|
552
|
+
console.print_exception()
|
|
553
|
+
raise CLIError(
|
|
554
|
+
"It appears you have a light install of localstack which only supports running in docker.\n"
|
|
555
|
+
"If you would like to use --host, please install localstack with Python using "
|
|
556
|
+
"`pip install localstack[runtime]` instead."
|
|
557
|
+
)
|
|
558
|
+
else:
|
|
559
|
+
# make sure to initialize the bootstrap environment and directories for the host (even if we're executing
|
|
560
|
+
# in Docker), to allow starting the container from within other containers (e.g., Github Codespaces).
|
|
561
|
+
config.OVERRIDE_IN_DOCKER = False
|
|
562
|
+
config.is_in_docker = False
|
|
563
|
+
config.dirs = config.init_directories()
|
|
564
|
+
|
|
565
|
+
# call hooks to prepare host (note that this call should stay below the config overrides above)
|
|
566
|
+
bootstrap.prepare_host(console)
|
|
567
|
+
|
|
568
|
+
# pass the parsed cli params to the start infra command
|
|
569
|
+
params = click.get_current_context().params
|
|
570
|
+
|
|
571
|
+
if network:
|
|
572
|
+
# reconciles the network config and makes sure that MAIN_DOCKER_NETWORK is set automatically if
|
|
573
|
+
# `--network` is set.
|
|
574
|
+
if config.MAIN_DOCKER_NETWORK:
|
|
575
|
+
if config.MAIN_DOCKER_NETWORK != network:
|
|
576
|
+
raise CLIError(
|
|
577
|
+
f"Values of MAIN_DOCKER_NETWORK={config.MAIN_DOCKER_NETWORK} and --network={network} "
|
|
578
|
+
f"do not match"
|
|
579
|
+
)
|
|
580
|
+
else:
|
|
581
|
+
config.MAIN_DOCKER_NETWORK = network
|
|
582
|
+
os.environ["MAIN_DOCKER_NETWORK"] = network
|
|
583
|
+
|
|
584
|
+
if detached:
|
|
585
|
+
bootstrap.start_infra_in_docker_detached(console, params)
|
|
586
|
+
else:
|
|
587
|
+
bootstrap.start_infra_in_docker(console, params)
|
|
588
|
+
|
|
589
|
+
|
|
590
|
+
@localstack.command(name="stop", short_help="Stop LocalStack")
|
|
591
|
+
@publish_invocation
|
|
592
|
+
def cmd_stop() -> None:
|
|
593
|
+
"""
|
|
594
|
+
Stops the current LocalStack runtime.
|
|
595
|
+
|
|
596
|
+
This command stops the currently running LocalStack docker container.
|
|
597
|
+
By default, this command looks for a container named `localstack-main` (which is the default
|
|
598
|
+
container name used by the `localstack start` command).
|
|
599
|
+
If your LocalStack container has a different name, set the config variable
|
|
600
|
+
`MAIN_CONTAINER_NAME`.
|
|
601
|
+
"""
|
|
602
|
+
from localstack_cli.utils.docker_utils import DOCKER_CLIENT
|
|
603
|
+
|
|
604
|
+
from ..utils.container_utils.container_client import NoSuchContainer
|
|
605
|
+
|
|
606
|
+
container_name = config.MAIN_CONTAINER_NAME
|
|
607
|
+
|
|
608
|
+
try:
|
|
609
|
+
DOCKER_CLIENT.stop_container(container_name)
|
|
610
|
+
console.print(f"container stopped: {container_name}")
|
|
611
|
+
except NoSuchContainer:
|
|
612
|
+
raise CLIError(
|
|
613
|
+
f'Expected a running LocalStack container named "{container_name}", but found none'
|
|
614
|
+
)
|
|
615
|
+
|
|
616
|
+
|
|
617
|
+
@localstack.command(name="restart", short_help="Restart LocalStack")
|
|
618
|
+
@publish_invocation
|
|
619
|
+
def cmd_restart() -> None:
|
|
620
|
+
"""
|
|
621
|
+
Restarts the current LocalStack runtime.
|
|
622
|
+
"""
|
|
623
|
+
url = config.external_service_url()
|
|
624
|
+
|
|
625
|
+
try:
|
|
626
|
+
response = requests.post(
|
|
627
|
+
f"{url}/_localstack/health",
|
|
628
|
+
json={"action": "restart"},
|
|
629
|
+
)
|
|
630
|
+
response.raise_for_status()
|
|
631
|
+
console.print("LocalStack restarted within the container.")
|
|
632
|
+
except requests.ConnectionError:
|
|
633
|
+
if config.DEBUG:
|
|
634
|
+
console.print_exception()
|
|
635
|
+
raise CLIError("could not restart the LocalStack container")
|
|
636
|
+
|
|
637
|
+
|
|
638
|
+
@localstack.command(
|
|
639
|
+
name="logs",
|
|
640
|
+
short_help="Show LocalStack logs",
|
|
641
|
+
)
|
|
642
|
+
@click.option(
|
|
643
|
+
"-f",
|
|
644
|
+
"--follow",
|
|
645
|
+
is_flag=True,
|
|
646
|
+
help="Block the terminal and follow the log output",
|
|
647
|
+
default=False,
|
|
648
|
+
)
|
|
649
|
+
@click.option(
|
|
650
|
+
"-n",
|
|
651
|
+
"--tail",
|
|
652
|
+
type=int,
|
|
653
|
+
help="Print only the last <N> lines of the log output",
|
|
654
|
+
default=None,
|
|
655
|
+
metavar="N",
|
|
656
|
+
)
|
|
657
|
+
@publish_invocation
|
|
658
|
+
def cmd_logs(follow: bool, tail: int) -> None:
|
|
659
|
+
"""
|
|
660
|
+
Show the logs of the current LocalStack runtime.
|
|
661
|
+
|
|
662
|
+
This command shows the logs of the currently running LocalStack docker container.
|
|
663
|
+
By default, this command looks for a container named `localstack-main` (which is the default
|
|
664
|
+
container name used by the `localstack start` command).
|
|
665
|
+
If your LocalStack container has a different name, set the config variable
|
|
666
|
+
`MAIN_CONTAINER_NAME`.
|
|
667
|
+
"""
|
|
668
|
+
from localstack_cli.utils.docker_utils import DOCKER_CLIENT
|
|
669
|
+
|
|
670
|
+
container_name = config.MAIN_CONTAINER_NAME
|
|
671
|
+
logfile = get_container_default_logfile_location(container_name)
|
|
672
|
+
|
|
673
|
+
if not DOCKER_CLIENT.is_container_running(container_name):
|
|
674
|
+
console.print("localstack container not running")
|
|
675
|
+
if os.path.exists(logfile):
|
|
676
|
+
console.print("printing logs from previous run")
|
|
677
|
+
with open(logfile) as fd:
|
|
678
|
+
for line in fd:
|
|
679
|
+
click.echo(line, nl=False)
|
|
680
|
+
sys.exit(1)
|
|
681
|
+
|
|
682
|
+
if follow:
|
|
683
|
+
num_lines = 0
|
|
684
|
+
for line in DOCKER_CLIENT.stream_container_logs(container_name):
|
|
685
|
+
print(line.decode("utf-8").rstrip("\r\n"))
|
|
686
|
+
num_lines += 1
|
|
687
|
+
if tail is not None and num_lines >= tail:
|
|
688
|
+
break
|
|
689
|
+
|
|
690
|
+
else:
|
|
691
|
+
logs = DOCKER_CLIENT.get_container_logs(container_name)
|
|
692
|
+
if tail is not None:
|
|
693
|
+
logs = "\n".join(logs.split("\n")[-tail:])
|
|
694
|
+
print(logs)
|
|
695
|
+
|
|
696
|
+
|
|
697
|
+
@localstack.command(name="wait", short_help="Wait for LocalStack")
|
|
698
|
+
@click.option(
|
|
699
|
+
"-t",
|
|
700
|
+
"--timeout",
|
|
701
|
+
type=float,
|
|
702
|
+
help="Only wait for <N> seconds before raising a timeout error",
|
|
703
|
+
default=None,
|
|
704
|
+
metavar="N",
|
|
705
|
+
)
|
|
706
|
+
@publish_invocation
|
|
707
|
+
def cmd_wait(timeout: float | None = None) -> None:
|
|
708
|
+
"""
|
|
709
|
+
Wait for the LocalStack runtime to be up and running.
|
|
710
|
+
|
|
711
|
+
This commands waits for a started LocalStack runtime to be up and running, ready to serve
|
|
712
|
+
requests.
|
|
713
|
+
By default, this command looks for a container named `localstack-main` (which is the default
|
|
714
|
+
container name used by the `localstack start` command).
|
|
715
|
+
If your LocalStack container has a different name, set the config variable
|
|
716
|
+
`MAIN_CONTAINER_NAME`.
|
|
717
|
+
"""
|
|
718
|
+
from localstack_cli.utils.bootstrap import wait_container_is_ready
|
|
719
|
+
|
|
720
|
+
if not wait_container_is_ready(timeout=timeout):
|
|
721
|
+
raise CLIError("timeout")
|
|
722
|
+
|
|
723
|
+
|
|
724
|
+
@localstack.command(name="ssh", short_help="Obtain a shell in LocalStack")
|
|
725
|
+
@publish_invocation
|
|
726
|
+
def cmd_ssh() -> None:
|
|
727
|
+
"""
|
|
728
|
+
Obtain a shell in the current LocalStack runtime.
|
|
729
|
+
|
|
730
|
+
This command starts a new interactive shell in the currently running LocalStack container.
|
|
731
|
+
By default, this command looks for a container named `localstack-main` (which is the default
|
|
732
|
+
container name used by the `localstack start` command).
|
|
733
|
+
If your LocalStack container has a different name, set the config variable
|
|
734
|
+
`MAIN_CONTAINER_NAME`.
|
|
735
|
+
"""
|
|
736
|
+
from localstack_cli.utils.docker_utils import DOCKER_CLIENT
|
|
737
|
+
|
|
738
|
+
if not DOCKER_CLIENT.is_container_running(config.MAIN_CONTAINER_NAME):
|
|
739
|
+
raise CLIError(
|
|
740
|
+
f'Expected a running LocalStack container named "{config.MAIN_CONTAINER_NAME}", but found none'
|
|
741
|
+
)
|
|
742
|
+
os.execlp("docker", "docker", "exec", "-it", config.MAIN_CONTAINER_NAME, "bash")
|
|
743
|
+
|
|
744
|
+
|
|
745
|
+
@localstack.group(name="update", short_help="Update LocalStack")
|
|
746
|
+
def localstack_update() -> None:
|
|
747
|
+
"""
|
|
748
|
+
Update different LocalStack components.
|
|
749
|
+
"""
|
|
750
|
+
pass
|
|
751
|
+
|
|
752
|
+
|
|
753
|
+
@localstack_update.command(name="all", short_help="Update all LocalStack components")
|
|
754
|
+
@click.pass_context
|
|
755
|
+
@publish_invocation
|
|
756
|
+
def cmd_update_all(ctx: click.Context) -> None:
|
|
757
|
+
"""
|
|
758
|
+
Update all LocalStack components.
|
|
759
|
+
|
|
760
|
+
This is the same as executing `localstack update localstack-cli` and
|
|
761
|
+
`localstack update docker-images`.
|
|
762
|
+
Updating the LocalStack CLI is currently only supported if the CLI
|
|
763
|
+
is installed and run via Python / PIP. If you used a different installation method,
|
|
764
|
+
please follow the instructions on https://docs.localstack.cloud/.
|
|
765
|
+
"""
|
|
766
|
+
ctx.invoke(localstack_update.get_command(ctx, "localstack-cli"))
|
|
767
|
+
ctx.invoke(localstack_update.get_command(ctx, "docker-images"))
|
|
768
|
+
|
|
769
|
+
|
|
770
|
+
@localstack_update.command(name="localstack-cli", short_help="Update LocalStack CLI")
|
|
771
|
+
@publish_invocation
|
|
772
|
+
def cmd_update_localstack_cli() -> None:
|
|
773
|
+
"""
|
|
774
|
+
Update the LocalStack CLI.
|
|
775
|
+
|
|
776
|
+
This command updates the LocalStack CLI. This is currently only supported if the CLI
|
|
777
|
+
is installed and run via Python / PIP. If you used a different installation method,
|
|
778
|
+
please follow the instructions on https://docs.localstack.cloud/.
|
|
779
|
+
"""
|
|
780
|
+
if is_frozen_bundle():
|
|
781
|
+
# "update" can only be performed if running from source / in a non-frozen interpreter
|
|
782
|
+
raise CLIError(
|
|
783
|
+
"The LocalStack CLI can only update itself if installed via PIP. "
|
|
784
|
+
"Please follow the instructions on https://docs.localstack.cloud/ to update your CLI."
|
|
785
|
+
)
|
|
786
|
+
|
|
787
|
+
import subprocess
|
|
788
|
+
from subprocess import CalledProcessError
|
|
789
|
+
|
|
790
|
+
console.rule("Updating LocalStack CLI")
|
|
791
|
+
with console.status("Updating LocalStack CLI..."):
|
|
792
|
+
try:
|
|
793
|
+
subprocess.check_output(
|
|
794
|
+
[sys.executable, "-m", "pip", "install", "--upgrade", "localstack"]
|
|
795
|
+
)
|
|
796
|
+
console.print(":heavy_check_mark: LocalStack CLI updated")
|
|
797
|
+
except CalledProcessError:
|
|
798
|
+
console.print(":heavy_multiplication_x: LocalStack CLI update failed", style="bold red")
|
|
799
|
+
|
|
800
|
+
|
|
801
|
+
@localstack_update.command(
|
|
802
|
+
name="docker-images", short_help="Update docker images LocalStack depends on"
|
|
803
|
+
)
|
|
804
|
+
@publish_invocation
|
|
805
|
+
def cmd_update_docker_images() -> None:
|
|
806
|
+
"""
|
|
807
|
+
Update all Docker images LocalStack depends on.
|
|
808
|
+
|
|
809
|
+
This command updates all Docker LocalStack docker images, as well as other Docker images
|
|
810
|
+
LocalStack depends on (and which have been used before / are present on the machine).
|
|
811
|
+
"""
|
|
812
|
+
from localstack_cli.utils.docker_utils import DOCKER_CLIENT
|
|
813
|
+
|
|
814
|
+
console.rule("Updating docker images")
|
|
815
|
+
|
|
816
|
+
all_images = DOCKER_CLIENT.get_docker_image_names(strip_latest=False)
|
|
817
|
+
image_prefixes = [
|
|
818
|
+
"localstack/",
|
|
819
|
+
"public.ecr.aws/lambda",
|
|
820
|
+
]
|
|
821
|
+
localstack_images = [
|
|
822
|
+
image
|
|
823
|
+
for image in all_images
|
|
824
|
+
if any(
|
|
825
|
+
image.startswith(image_prefix) or image.startswith(f"docker.io/{image_prefix}")
|
|
826
|
+
for image_prefix in image_prefixes
|
|
827
|
+
)
|
|
828
|
+
and not image.endswith(":<none>") # ignore dangling images
|
|
829
|
+
]
|
|
830
|
+
update_images(localstack_images)
|
|
831
|
+
|
|
832
|
+
|
|
833
|
+
def update_images(image_list: list[str]) -> None:
|
|
834
|
+
from rich.markup import escape
|
|
835
|
+
from rich.progress import MofNCompleteColumn, Progress
|
|
836
|
+
|
|
837
|
+
from localstack_cli.utils.container_utils.container_client import ContainerException
|
|
838
|
+
from localstack_cli.utils.docker_utils import DOCKER_CLIENT
|
|
839
|
+
|
|
840
|
+
updated_count = 0
|
|
841
|
+
failed_count = 0
|
|
842
|
+
progress = Progress(
|
|
843
|
+
*Progress.get_default_columns(), MofNCompleteColumn(), transient=True, console=console
|
|
844
|
+
)
|
|
845
|
+
with progress:
|
|
846
|
+
for image in progress.track(image_list, description="Processing image..."):
|
|
847
|
+
try:
|
|
848
|
+
updated = False
|
|
849
|
+
hash_before_pull = DOCKER_CLIENT.inspect_image(image_name=image, pull=False)["Id"]
|
|
850
|
+
DOCKER_CLIENT.pull_image(image)
|
|
851
|
+
if (
|
|
852
|
+
hash_before_pull
|
|
853
|
+
!= DOCKER_CLIENT.inspect_image(image_name=image, pull=False)["Id"]
|
|
854
|
+
):
|
|
855
|
+
updated = True
|
|
856
|
+
updated_count += 1
|
|
857
|
+
console.print(
|
|
858
|
+
f":heavy_check_mark: Image {escape(image)} {'updated' if updated else 'up-to-date'}.",
|
|
859
|
+
style="bold" if updated else None,
|
|
860
|
+
highlight=False,
|
|
861
|
+
)
|
|
862
|
+
except ContainerException as e:
|
|
863
|
+
console.print(
|
|
864
|
+
f":heavy_multiplication_x: Image {escape(image)} pull failed: {e.message}",
|
|
865
|
+
style="bold red",
|
|
866
|
+
highlight=False,
|
|
867
|
+
)
|
|
868
|
+
failed_count += 1
|
|
869
|
+
console.rule()
|
|
870
|
+
console.print(
|
|
871
|
+
f"Images updated: {updated_count}, Images failed: {failed_count}, total images processed: {len(image_list)}."
|
|
872
|
+
)
|
|
873
|
+
|
|
874
|
+
|
|
875
|
+
@localstack.command(name="completion", short_help="CLI shell completion")
|
|
876
|
+
@click.pass_context
|
|
877
|
+
@click.argument(
|
|
878
|
+
"shell", required=True, type=click.Choice(["bash", "zsh", "fish"], case_sensitive=False)
|
|
879
|
+
)
|
|
880
|
+
@publish_invocation
|
|
881
|
+
def localstack_completion(ctx: click.Context, shell: str) -> None:
|
|
882
|
+
"""
|
|
883
|
+
Print shell completion code for the specified shell (bash, zsh, or fish).
|
|
884
|
+
The shell code must be evaluated to enable the interactive shell completion of LocalStack CLI commands.
|
|
885
|
+
This is usually done by sourcing it from the .bash_profile.
|
|
886
|
+
|
|
887
|
+
\b
|
|
888
|
+
Examples:
|
|
889
|
+
# Bash
|
|
890
|
+
## Bash completion on Linux depends on the 'bash-completion' package.
|
|
891
|
+
## Write the LocalStack CLI completion code for bash to a file and source it from .bash_profile
|
|
892
|
+
localstack completion bash > ~/.localstack/completion.bash.inc
|
|
893
|
+
printf "
|
|
894
|
+
# LocalStack CLI bash completion
|
|
895
|
+
source '$HOME/.localstack/completion.bash.inc'
|
|
896
|
+
" >> $HOME/.bash_profile
|
|
897
|
+
source $HOME/.bash_profile
|
|
898
|
+
\b
|
|
899
|
+
# zsh
|
|
900
|
+
## Set the LocalStack completion code for zsh to autoload on startup:
|
|
901
|
+
localstack completion zsh > "${fpath[1]}/_localstack"
|
|
902
|
+
\b
|
|
903
|
+
# fish
|
|
904
|
+
## Set the LocalStack completion code for fish to autoload on startup:
|
|
905
|
+
localstack completion fish > ~/.config/fish/completions/localstack.fish
|
|
906
|
+
"""
|
|
907
|
+
|
|
908
|
+
# lookup the completion, raise an error if the given completion is not found
|
|
909
|
+
import click.shell_completion
|
|
910
|
+
|
|
911
|
+
comp_cls = click.shell_completion.get_completion_class(shell)
|
|
912
|
+
if comp_cls is None:
|
|
913
|
+
raise CLIError("Completion for given shell could not be found.")
|
|
914
|
+
|
|
915
|
+
# Click's program name is the base path of sys.argv[0]
|
|
916
|
+
path = sys.argv[0]
|
|
917
|
+
prog_name = os.path.basename(path)
|
|
918
|
+
|
|
919
|
+
# create the completion variable according to the docs
|
|
920
|
+
# https://click.palletsprojects.com/en/8.1.x/shell-completion/#enabling-completion
|
|
921
|
+
complete_var = f"_{prog_name}_COMPLETE".replace("-", "_").upper()
|
|
922
|
+
|
|
923
|
+
# instantiate the completion class and print the completion source
|
|
924
|
+
comp = comp_cls(ctx.command, {}, prog_name, complete_var)
|
|
925
|
+
click.echo(comp.source())
|
|
926
|
+
|
|
927
|
+
|
|
928
|
+
def print_version() -> None:
|
|
929
|
+
console.print(f"- [bold]LocalStack CLI:[/bold] [blue]{VERSION}[/blue]")
|
|
930
|
+
|
|
931
|
+
|
|
932
|
+
def print_profile() -> None:
|
|
933
|
+
if config.LOADED_PROFILES:
|
|
934
|
+
console.print(f"- [bold]Profile:[/bold] [blue]{', '.join(config.LOADED_PROFILES)}[/blue]")
|
|
935
|
+
|
|
936
|
+
|
|
937
|
+
def print_app() -> None:
|
|
938
|
+
console.print("- [bold]App:[/bold] https://app.localstack.cloud")
|
|
939
|
+
|
|
940
|
+
|
|
941
|
+
def print_banner() -> None:
|
|
942
|
+
print(BANNER)
|
|
943
|
+
|
|
944
|
+
|
|
945
|
+
def is_frozen_bundle() -> bool:
|
|
946
|
+
"""
|
|
947
|
+
:return: true if we are currently running in a frozen bundle / a pyinstaller binary.
|
|
948
|
+
"""
|
|
949
|
+
# check if we are in a PyInstaller binary
|
|
950
|
+
# https://pyinstaller.org/en/stable/runtime-information.html
|
|
951
|
+
return getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS")
|