Skip to content

Commit acead98

Browse files
committed
Add remaining implementation for demo
1 parent 680b0ac commit acead98

File tree

1 file changed

+132
-12
lines changed

1 file changed

+132
-12
lines changed

localstack-core/localstack/cli/localstack.py

Lines changed: 132 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import os
44
import sys
55
import traceback
6+
from subprocess import CalledProcessError
67
from typing import Dict, List, Optional, Tuple, TypedDict
78

89
import click
@@ -78,6 +79,24 @@ def _run_external(ctx, args):
7879
add_help_option=False,
7980
)
8081

82+
# If user tries 'aws' or 'tf' but tool isn't installed, show install hint
83+
if cmd_name in ("aws", "tf"):
84+
from click.decorators import pass_context
85+
86+
def _install_hint(ctx, args):
87+
raise CLIError(
88+
f"Tool '{cmd_name}' not installed. "
89+
f"Run 'localstack tool install {cmd_name}' to install it."
90+
)
91+
92+
_install_hint = pass_context(_install_hint)
93+
return click.Command(
94+
name=cmd_name,
95+
params=[click.Argument(["args"], nargs=-1)],
96+
callback=_install_hint,
97+
help=f"Install the helper tool '{cmd_name}' via 'localstack tool install {cmd_name}'",
98+
add_help_option=False,
99+
)
81100
return None
82101

83102
def invoke(self, ctx: click.Context):
@@ -891,31 +910,111 @@ def localstack_tool() -> None:
891910

892911

893912
@localstack_tool.command(name="install", short_help="Install a helper tool")
894-
@click.argument("tool", type=click.Choice(["aws"], case_sensitive=False))
913+
@click.argument("tool", type=click.Choice(["aws", "tf"], case_sensitive=False))
895914
@publish_invocation
896915
def cmd_tool_install(tool: str) -> None:
897916
"""
898917
Install a helper tool. Currently supported:
899918
aws - installs 'awscli-local' via pip.
900919
"""
901920
mapping = {
902-
"aws": "awscli-local",
921+
"aws": "https://github.com/localstack/awscli-local.git@localstack-aws-rename",
922+
"tf": "https://github.com/localstack/terraform-local.git@localstack-tf-rename",
903923
}
904-
package = mapping.get(tool.lower())
905-
if not package:
924+
repo_url = mapping.get(tool.lower())
925+
if not repo_url:
906926
raise CLIError(f"Unknown tool: {tool}")
907927

908-
console.rule(f"Installing {package}")
928+
console.rule(f"Installing {tool}")
909929
try:
930+
import os
910931
import subprocess
911-
import sys
912-
from subprocess import CalledProcessError
913932

914-
subprocess.check_call([sys.executable, "-m", "pip", "install", package])
915-
console.print(f":heavy_check_mark: Installed {package}")
916-
except CalledProcessError:
917-
console.print(f":heavy_multiplication_x: Failed to install {package}", style="bold red")
918-
sys.exit(1)
933+
prefix = os.path.expanduser("~/.localstack")
934+
os.makedirs(prefix, exist_ok=True)
935+
python_bin = "/Users/silvio/.local/share/mise/installs/python/3.11.12/bin/python"
936+
subprocess.check_call(
937+
[
938+
python_bin,
939+
"-m",
940+
"pip",
941+
"install",
942+
"--upgrade",
943+
"--force-reinstall",
944+
f"git+{repo_url}",
945+
"--prefix",
946+
prefix,
947+
]
948+
)
949+
console.print(f":heavy_check_mark: Installed {tool} from {repo_url} into {prefix}")
950+
except CalledProcessError as e:
951+
raise CLIError(f"Failed to install {tool} from {repo_url}") from e
952+
953+
954+
# Uninstall command for helper tools
955+
@localstack_tool.command(name="uninstall", short_help="Uninstall a helper tool")
956+
@click.argument("tool", type=click.Choice(["aws", "tf"], case_sensitive=False))
957+
@publish_invocation
958+
def cmd_tool_uninstall(tool: str) -> None:
959+
"""
960+
Uninstall a helper tool by removing its installed files from ~/.localstack.
961+
Supported:
962+
aws - uninstalls 'awscli-local'
963+
tf - uninstalls 'terraform-local'
964+
"""
965+
import glob
966+
import os
967+
import shutil
968+
import sys
969+
970+
from localstack.cli.exceptions import CLIError
971+
972+
tool_map = {
973+
"aws": {"pkg_name": "localstack_awscli", "scripts": ["localstack-aws"]},
974+
"tf": {"pkg_name": "localstack_terraform", "scripts": ["localstack-tf"]},
975+
}
976+
info = tool_map.get(tool.lower())
977+
if not info:
978+
raise CLIError(f"Unknown tool: {tool}")
979+
980+
prefix = os.path.expanduser("~/.localstack")
981+
bin_dir = os.path.join(prefix, "bin")
982+
lib_dir = os.path.join(
983+
prefix, "lib", f"python{sys.version_info.major}.{sys.version_info.minor}", "site-packages"
984+
)
985+
986+
console.rule(f"Uninstalling {tool}")
987+
988+
# Remove executables
989+
for script in info["scripts"]:
990+
path = os.path.join(bin_dir, script)
991+
if os.path.exists(path):
992+
try:
993+
os.remove(path)
994+
os.remove(path + ".bat")
995+
console.log(f"Removed script: {path}")
996+
except OSError:
997+
console.log(f"Failed to remove script: {path}")
998+
999+
# Remove package directories and metadata
1000+
patterns = [
1001+
f"{info['pkg_name']}*",
1002+
f"{info['pkg_name'].replace('_', '-')}-*.dist-info*",
1003+
f"{info['pkg_name']}-*.egg-info*",
1004+
]
1005+
for pattern in patterns:
1006+
for item in glob.glob(os.path.join(lib_dir, pattern)):
1007+
try:
1008+
if os.path.isdir(item):
1009+
shutil.rmtree(item)
1010+
console.log(f"Removed directory: {item}")
1011+
else:
1012+
os.remove(item)
1013+
console.log(f"Removed file: {item}")
1014+
except OSError:
1015+
console.log(f"Failed to remove: {item}")
1016+
1017+
console.print(f":heavy_check_mark: Uninstalled {tool}")
9191018

9201019

9211020
@localstack_tool.command(name="ls", short_help="List available helper tools")
@@ -948,6 +1047,27 @@ def cmd_tool_ls() -> None:
9481047
console.print("No external LocalStack tools found.")
9491048

9501049

1050+
# --- new command: tool init ---
1051+
@localstack_tool.command(name="init", short_help="Initialize environment for helper tools")
1052+
@publish_invocation
1053+
def cmd_tool_init() -> None:
1054+
"""
1055+
Print shell commands to configure PATH and PYTHONPATH for LocalStack helper tools.
1056+
"""
1057+
import os
1058+
import sys
1059+
1060+
prefix = os.path.expanduser("~/.localstack")
1061+
bin_dir = os.path.join(prefix, "bin")
1062+
py_version = f"{sys.version_info.major}.{sys.version_info.minor}"
1063+
site_packages = os.path.join(prefix, "lib", f"python{py_version}", "site-packages")
1064+
1065+
console.print("To use LocalStack helper tools, add the following to your shell profile:")
1066+
console.print("")
1067+
console.print(f' export PATH="{bin_dir}:$PATH"')
1068+
console.print(f' export PYTHONPATH="{site_packages}:$PYTHONPATH"')
1069+
1070+
9511071
@localstack.command(name="completion", short_help="CLI shell completion")
9521072
@click.pass_context
9531073
@click.argument(

0 commit comments

Comments
 (0)