toast-cli 3.0.0.dev0__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.
- toast/__init__.py +83 -0
- toast/__main__.py +6 -0
- toast/helpers.py +38 -0
- toast/plugins/__init__.py +1 -0
- toast/plugins/am_plugin.py +23 -0
- toast/plugins/base_plugin.py +31 -0
- toast/plugins/cdw_plugin.py +33 -0
- toast/plugins/ctx_plugin.py +62 -0
- toast/plugins/env_plugin.py +212 -0
- toast/plugins/git_plugin.py +92 -0
- toast/plugins/region_plugin.py +32 -0
- toast/plugins/ssm_plugin.py +14 -0
- toast/plugins/update_plugin.py +14 -0
- toast/plugins/utils.py +12 -0
- toast_cli-3.0.0.dev0.dist-info/LICENSE +674 -0
- toast_cli-3.0.0.dev0.dist-info/METADATA +161 -0
- toast_cli-3.0.0.dev0.dist-info/RECORD +20 -0
- toast_cli-3.0.0.dev0.dist-info/WHEEL +5 -0
- toast_cli-3.0.0.dev0.dist-info/entry_points.txt +2 -0
- toast_cli-3.0.0.dev0.dist-info/top_level.txt +1 -0
toast/__init__.py
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
import importlib
|
|
5
|
+
import inspect
|
|
6
|
+
import os
|
|
7
|
+
import pkgutil
|
|
8
|
+
import sys
|
|
9
|
+
from typing import List, Type
|
|
10
|
+
from toast.helpers import CustomHelpGroup
|
|
11
|
+
|
|
12
|
+
def discover_and_load_plugins(plugins_package_name: str = "toast.plugins") -> List[Type]:
|
|
13
|
+
"""
|
|
14
|
+
Dynamically discover and load all plugin classes from the plugins package.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
plugins_package_name: Name of the plugins package to search
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
List of plugin classes that extend BasePlugin
|
|
21
|
+
"""
|
|
22
|
+
from toast.plugins.base_plugin import BasePlugin
|
|
23
|
+
|
|
24
|
+
discovered_plugins = []
|
|
25
|
+
|
|
26
|
+
try:
|
|
27
|
+
# Import the plugins package
|
|
28
|
+
plugins_package = importlib.import_module(plugins_package_name)
|
|
29
|
+
plugins_path = os.path.dirname(plugins_package.__file__)
|
|
30
|
+
|
|
31
|
+
# Discover all modules in the plugins package
|
|
32
|
+
for _, name, is_pkg in pkgutil.iter_modules([plugins_path]):
|
|
33
|
+
# Skip the base_plugin module and __init__.py
|
|
34
|
+
if name == "base_plugin" or name == "__init__" or name == "utils":
|
|
35
|
+
continue
|
|
36
|
+
|
|
37
|
+
# Import the module
|
|
38
|
+
module_name = f"{plugins_package_name}.{name}"
|
|
39
|
+
try:
|
|
40
|
+
module = importlib.import_module(module_name)
|
|
41
|
+
|
|
42
|
+
# Find all classes in the module that are subclasses of BasePlugin
|
|
43
|
+
for item_name, item in inspect.getmembers(module, inspect.isclass):
|
|
44
|
+
if (issubclass(item, BasePlugin) and
|
|
45
|
+
item is not BasePlugin and
|
|
46
|
+
item.__module__ == module_name):
|
|
47
|
+
discovered_plugins.append(item)
|
|
48
|
+
except ImportError as e:
|
|
49
|
+
click.echo(f"Error loading plugin module {module_name}: {e}", err=True)
|
|
50
|
+
|
|
51
|
+
except ImportError as e:
|
|
52
|
+
click.echo(f"Error loading plugins package {plugins_package_name}: {e}", err=True)
|
|
53
|
+
|
|
54
|
+
return discovered_plugins
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@click.group(cls=CustomHelpGroup)
|
|
58
|
+
def toast_cli():
|
|
59
|
+
"""
|
|
60
|
+
Toast command-line tool with dynamically loaded plugins.
|
|
61
|
+
"""
|
|
62
|
+
pass
|
|
63
|
+
|
|
64
|
+
def main():
|
|
65
|
+
# Discover and load all plugins
|
|
66
|
+
plugins = discover_and_load_plugins()
|
|
67
|
+
|
|
68
|
+
if not plugins:
|
|
69
|
+
click.echo("No plugins were discovered", err=True)
|
|
70
|
+
sys.exit(1)
|
|
71
|
+
|
|
72
|
+
# Register each plugin with the CLI
|
|
73
|
+
for plugin_class in plugins:
|
|
74
|
+
try:
|
|
75
|
+
plugin_class.register(toast_cli)
|
|
76
|
+
except Exception as e:
|
|
77
|
+
click.echo(f"Error registering plugin {plugin_class.__name__}: {e}", err=True)
|
|
78
|
+
|
|
79
|
+
# Run the CLI
|
|
80
|
+
toast_cli()
|
|
81
|
+
|
|
82
|
+
if __name__ == "__main__":
|
|
83
|
+
main()
|
toast/__main__.py
ADDED
toast/helpers.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
import os
|
|
5
|
+
|
|
6
|
+
def display_logo():
|
|
7
|
+
"""Display the toast.sh ASCII logo"""
|
|
8
|
+
logo = """
|
|
9
|
+
_ _ _
|
|
10
|
+
| |_ ___ __ _ ___| |_ ___| |__
|
|
11
|
+
| __/ _ \ / _' / __| __| / __| '_ \\
|
|
12
|
+
| || (_) | (_| \__ \ |_ _\__ \ | | |
|
|
13
|
+
\__\___/ \__,_|___/\__(-)___/_| |_| v{0}
|
|
14
|
+
""".format(get_version())
|
|
15
|
+
click.echo(logo)
|
|
16
|
+
click.echo("=" * 80)
|
|
17
|
+
|
|
18
|
+
def get_version():
|
|
19
|
+
"""Get the version from the VERSION file"""
|
|
20
|
+
try:
|
|
21
|
+
version_file = os.path.join(os.path.dirname(__file__), "..", "VERSION")
|
|
22
|
+
if os.path.exists(version_file):
|
|
23
|
+
with open(version_file, "r") as f:
|
|
24
|
+
version = f.read().strip()
|
|
25
|
+
return version
|
|
26
|
+
return "3.0.0"
|
|
27
|
+
except Exception:
|
|
28
|
+
return "3.0.0"
|
|
29
|
+
|
|
30
|
+
class CustomHelpCommand(click.Command):
|
|
31
|
+
def get_help(self, ctx):
|
|
32
|
+
display_logo()
|
|
33
|
+
return super().get_help(ctx)
|
|
34
|
+
|
|
35
|
+
class CustomHelpGroup(click.Group):
|
|
36
|
+
def get_help(self, ctx):
|
|
37
|
+
display_logo()
|
|
38
|
+
return super().get_help(ctx)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Make the plugins directory a Python package
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
import subprocess
|
|
5
|
+
from toast.plugins.base_plugin import BasePlugin
|
|
6
|
+
|
|
7
|
+
class AmPlugin(BasePlugin):
|
|
8
|
+
"""Plugin for 'am' command - shows AWS caller identity."""
|
|
9
|
+
|
|
10
|
+
name = "am"
|
|
11
|
+
help = "Show AWS caller identity"
|
|
12
|
+
|
|
13
|
+
@classmethod
|
|
14
|
+
def execute(cls, **kwargs):
|
|
15
|
+
try:
|
|
16
|
+
result = subprocess.run(["aws", "sts", "get-caller-identity"], capture_output=True, text=True)
|
|
17
|
+
if result.returncode == 0:
|
|
18
|
+
formatted_json = subprocess.run(["jq", "-C", "."], input=result.stdout, capture_output=True, text=True)
|
|
19
|
+
click.echo(formatted_json.stdout)
|
|
20
|
+
else:
|
|
21
|
+
click.echo("Error fetching AWS caller identity.")
|
|
22
|
+
except Exception as e:
|
|
23
|
+
click.echo(f"Error fetching AWS caller identity: {e}")
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
|
|
5
|
+
class BasePlugin:
|
|
6
|
+
"""Base class for all plugins."""
|
|
7
|
+
|
|
8
|
+
name = None # Command name
|
|
9
|
+
help = None # Command help text
|
|
10
|
+
|
|
11
|
+
@classmethod
|
|
12
|
+
def register(cls, cli_group):
|
|
13
|
+
"""Register the plugin with the CLI group."""
|
|
14
|
+
if not cls.name:
|
|
15
|
+
raise ValueError(f"Plugin {cls.__name__} must define a name")
|
|
16
|
+
|
|
17
|
+
# Use regular Command class to avoid showing logo for subcommands
|
|
18
|
+
@cli_group.command(name=cls.name, help=cls.help, cls=click.Command)
|
|
19
|
+
@cls.get_arguments
|
|
20
|
+
def command(**kwargs):
|
|
21
|
+
return cls.execute(**kwargs)
|
|
22
|
+
|
|
23
|
+
@classmethod
|
|
24
|
+
def get_arguments(cls, func):
|
|
25
|
+
"""Define command arguments. Override in subclass."""
|
|
26
|
+
return func
|
|
27
|
+
|
|
28
|
+
@classmethod
|
|
29
|
+
def execute(cls, **kwargs):
|
|
30
|
+
"""Execute the command. Override in subclass."""
|
|
31
|
+
raise NotImplementedError(f"Plugin {cls.__name__} must implement execute method")
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
import subprocess
|
|
5
|
+
import os
|
|
6
|
+
from toast.plugins.base_plugin import BasePlugin
|
|
7
|
+
from toast.plugins.utils import select_from_list
|
|
8
|
+
|
|
9
|
+
class CdwPlugin(BasePlugin):
|
|
10
|
+
"""Plugin for 'cdw' command - helps navigate to workspace directories."""
|
|
11
|
+
|
|
12
|
+
name = "cdw"
|
|
13
|
+
help = "Navigate to a workspace directory"
|
|
14
|
+
|
|
15
|
+
@classmethod
|
|
16
|
+
def execute(cls, **kwargs):
|
|
17
|
+
workspace_dir = os.path.expanduser("~/workspace")
|
|
18
|
+
if not os.path.exists(workspace_dir):
|
|
19
|
+
os.makedirs(workspace_dir)
|
|
20
|
+
|
|
21
|
+
result = subprocess.run(["find", workspace_dir, "-mindepth", "1", "-maxdepth", "2", "-type", "d"], capture_output=True, text=True)
|
|
22
|
+
directories = sorted(result.stdout.splitlines())
|
|
23
|
+
|
|
24
|
+
if not directories:
|
|
25
|
+
click.echo("No directories found in ~/workspace.")
|
|
26
|
+
return
|
|
27
|
+
|
|
28
|
+
selected_dir = select_from_list(directories, "Select a directory")
|
|
29
|
+
|
|
30
|
+
if selected_dir:
|
|
31
|
+
click.echo(selected_dir)
|
|
32
|
+
else:
|
|
33
|
+
click.echo("No directory selected.", err=True)
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
import subprocess
|
|
5
|
+
from toast.plugins.base_plugin import BasePlugin
|
|
6
|
+
from toast.plugins.utils import select_from_list
|
|
7
|
+
|
|
8
|
+
class CtxPlugin(BasePlugin):
|
|
9
|
+
"""Plugin for 'ctx' command - manages Kubernetes contexts."""
|
|
10
|
+
|
|
11
|
+
name = "ctx"
|
|
12
|
+
help = "Manage Kubernetes contexts"
|
|
13
|
+
|
|
14
|
+
@classmethod
|
|
15
|
+
def execute(cls, **kwargs):
|
|
16
|
+
result = subprocess.run(["kubectl", "config", "get-contexts", "-o=name"], capture_output=True, text=True)
|
|
17
|
+
if result.returncode != 0:
|
|
18
|
+
click.echo("Error fetching Kubernetes contexts. Is kubectl configured correctly?")
|
|
19
|
+
return
|
|
20
|
+
|
|
21
|
+
contexts = sorted(result.stdout.splitlines())
|
|
22
|
+
contexts.append("[New...]")
|
|
23
|
+
if len(contexts) > 1:
|
|
24
|
+
contexts.append("[Del...]")
|
|
25
|
+
|
|
26
|
+
selected_ctx = select_from_list(contexts, "Select a Kubernetes context")
|
|
27
|
+
|
|
28
|
+
if selected_ctx == "[New...]":
|
|
29
|
+
result = subprocess.run(["aws", "eks", "list-clusters", "--query", "clusters", "--output", "text"], capture_output=True, text=True)
|
|
30
|
+
if result.returncode != 0:
|
|
31
|
+
click.echo("Error fetching EKS clusters.")
|
|
32
|
+
return
|
|
33
|
+
|
|
34
|
+
clusters = sorted(result.stdout.split())
|
|
35
|
+
if not clusters:
|
|
36
|
+
click.echo("No EKS clusters found.")
|
|
37
|
+
return
|
|
38
|
+
|
|
39
|
+
selected_cluster = select_from_list(clusters, "Select an EKS cluster")
|
|
40
|
+
|
|
41
|
+
if selected_cluster:
|
|
42
|
+
subprocess.run(["aws", "eks", "update-kubeconfig", "--name", selected_cluster, "--alias", selected_cluster])
|
|
43
|
+
click.echo(f"Updated kubeconfig for {selected_cluster}")
|
|
44
|
+
else:
|
|
45
|
+
click.echo("No cluster selected.")
|
|
46
|
+
elif selected_ctx == "[Del...]":
|
|
47
|
+
delete_contexts = [ctx for ctx in contexts if ctx not in ("[New...]", "[Del...]")]
|
|
48
|
+
delete_contexts.append("[All...]")
|
|
49
|
+
selected_to_delete = select_from_list(delete_contexts, "Select a context to delete")
|
|
50
|
+
if selected_to_delete == "[All...]":
|
|
51
|
+
subprocess.run(["kubectl", "config", "unset", "contexts"])
|
|
52
|
+
click.echo("Deleted all Kubernetes contexts.")
|
|
53
|
+
elif selected_to_delete:
|
|
54
|
+
subprocess.run(["kubectl", "config", "delete-context", selected_to_delete])
|
|
55
|
+
click.echo(f"Deleted Kubernetes context: {selected_to_delete}")
|
|
56
|
+
else:
|
|
57
|
+
click.echo("No context selected for deletion.")
|
|
58
|
+
elif selected_ctx:
|
|
59
|
+
subprocess.run(["kubectl", "config", "use-context", selected_ctx])
|
|
60
|
+
click.echo(f"Switched to Kubernetes context: {selected_ctx}")
|
|
61
|
+
else:
|
|
62
|
+
click.echo("No context selected.")
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
import subprocess
|
|
5
|
+
import os
|
|
6
|
+
import dotenv
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from toast.plugins.base_plugin import BasePlugin
|
|
9
|
+
from toast.plugins.utils import select_from_list
|
|
10
|
+
|
|
11
|
+
class EnvPlugin(BasePlugin):
|
|
12
|
+
"""Plugin for 'env' command - sets environment."""
|
|
13
|
+
|
|
14
|
+
name = "env"
|
|
15
|
+
help = "Set environment with AWS profile"
|
|
16
|
+
|
|
17
|
+
@classmethod
|
|
18
|
+
def get_arguments(cls, func):
|
|
19
|
+
func = click.argument('env_name', required=False)(func)
|
|
20
|
+
return func
|
|
21
|
+
|
|
22
|
+
@classmethod
|
|
23
|
+
def execute(cls, env_name=None, **kwargs):
|
|
24
|
+
# Try to get AWS_ENV_PATH from .env file
|
|
25
|
+
dotenv_path = Path('.env')
|
|
26
|
+
env_path = None
|
|
27
|
+
|
|
28
|
+
if dotenv_path.exists():
|
|
29
|
+
dotenv.load_dotenv(dotenv_path)
|
|
30
|
+
env_path = os.environ.get("AWS_ENV_PATH")
|
|
31
|
+
|
|
32
|
+
# If AWS_ENV_PATH is not set, create it
|
|
33
|
+
if not env_path:
|
|
34
|
+
# Get username using whoami command
|
|
35
|
+
result = subprocess.run(["whoami"], capture_output=True, text=True)
|
|
36
|
+
default_username = result.stdout.strip()
|
|
37
|
+
|
|
38
|
+
# Create github.com directory if it doesn't exist
|
|
39
|
+
github_dir = os.path.expanduser("~/workspace/github.com")
|
|
40
|
+
if not os.path.exists(github_dir):
|
|
41
|
+
os.makedirs(github_dir, exist_ok=True)
|
|
42
|
+
click.echo(f"Created directory: {github_dir}")
|
|
43
|
+
|
|
44
|
+
# Ask user to input username or use provided env_name as username
|
|
45
|
+
if not env_name:
|
|
46
|
+
click.echo(f"Enter GitHub username (default: {default_username}):")
|
|
47
|
+
input_username = input().strip()
|
|
48
|
+
username = input_username if input_username else default_username
|
|
49
|
+
else:
|
|
50
|
+
# Check if env_name might be a profile name rather than username
|
|
51
|
+
potential_path = os.path.expanduser(f"~/workspace/github.com/{default_username}/keys/env/{env_name}")
|
|
52
|
+
if os.path.exists(potential_path):
|
|
53
|
+
# env_name is likely a profile name, use default username
|
|
54
|
+
username = default_username
|
|
55
|
+
else:
|
|
56
|
+
# env_name is likely a username
|
|
57
|
+
username = env_name
|
|
58
|
+
|
|
59
|
+
# Set AWS_ENV_PATH
|
|
60
|
+
env_path = os.path.expanduser(f"~/workspace/github.com/{username}/keys/env")
|
|
61
|
+
|
|
62
|
+
# Create directory if it doesn't exist
|
|
63
|
+
env_dir = os.path.dirname(env_path)
|
|
64
|
+
if not os.path.exists(env_dir):
|
|
65
|
+
os.makedirs(env_dir, exist_ok=True)
|
|
66
|
+
click.echo(f"Created directory: {env_dir}")
|
|
67
|
+
|
|
68
|
+
# Update .env file
|
|
69
|
+
with open(dotenv_path, 'a+') as f:
|
|
70
|
+
f.seek(0)
|
|
71
|
+
content = f.read()
|
|
72
|
+
if "AWS_ENV_PATH" not in content:
|
|
73
|
+
if content and not content.endswith('\n'):
|
|
74
|
+
f.write('\n')
|
|
75
|
+
f.write(f"AWS_ENV_PATH={env_path}\n")
|
|
76
|
+
|
|
77
|
+
# Export the environment variable
|
|
78
|
+
os.environ["AWS_ENV_PATH"] = env_path
|
|
79
|
+
|
|
80
|
+
# List available profiles if env_path exists
|
|
81
|
+
if os.path.exists(env_path):
|
|
82
|
+
try:
|
|
83
|
+
profiles = [f for f in os.listdir(env_path) if os.path.isfile(os.path.join(env_path, f))]
|
|
84
|
+
|
|
85
|
+
if not profiles:
|
|
86
|
+
click.echo(f"No profiles found in {env_path}")
|
|
87
|
+
return
|
|
88
|
+
|
|
89
|
+
if not env_name or env_name not in profiles:
|
|
90
|
+
selected_profile = select_from_list(profiles, "Select an AWS profile")
|
|
91
|
+
if selected_profile:
|
|
92
|
+
env_name = selected_profile
|
|
93
|
+
else:
|
|
94
|
+
click.echo("No profile selected.")
|
|
95
|
+
return
|
|
96
|
+
|
|
97
|
+
# Load the selected profile and environment variables
|
|
98
|
+
profile_path = os.path.join(env_path, env_name)
|
|
99
|
+
aws_access_key_id = None
|
|
100
|
+
aws_secret_access_key = None
|
|
101
|
+
aws_region = None
|
|
102
|
+
|
|
103
|
+
if os.path.exists(profile_path):
|
|
104
|
+
with open(profile_path, 'r') as f:
|
|
105
|
+
for line in f:
|
|
106
|
+
if '=' in line:
|
|
107
|
+
key, value = line.strip().split('=', 1)
|
|
108
|
+
os.environ[key] = value
|
|
109
|
+
|
|
110
|
+
# Capture AWS credentials for config file
|
|
111
|
+
if key == "AWS_ACCESS_KEY_ID":
|
|
112
|
+
aws_access_key_id = value
|
|
113
|
+
elif key == "AWS_SECRET_ACCESS_KEY":
|
|
114
|
+
aws_secret_access_key = value
|
|
115
|
+
elif key == "AWS_REGION":
|
|
116
|
+
aws_region = value
|
|
117
|
+
|
|
118
|
+
# Ensure AWS CLI config directory exists
|
|
119
|
+
aws_config_dir = os.path.expanduser("~/.aws")
|
|
120
|
+
if not os.path.exists(aws_config_dir):
|
|
121
|
+
os.makedirs(aws_config_dir, exist_ok=True)
|
|
122
|
+
|
|
123
|
+
# Update AWS config and credentials files if we have the necessary info
|
|
124
|
+
if aws_access_key_id and aws_secret_access_key:
|
|
125
|
+
# Update credentials file for both profile name and default
|
|
126
|
+
credentials_file = os.path.join(aws_config_dir, "credentials")
|
|
127
|
+
|
|
128
|
+
# Update the named profile
|
|
129
|
+
cls._update_aws_file(credentials_file, env_name, {
|
|
130
|
+
"aws_access_key_id": aws_access_key_id,
|
|
131
|
+
"aws_secret_access_key": aws_secret_access_key
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
# Also update default profile with the same credentials
|
|
135
|
+
cls._update_aws_file(credentials_file, "default", {
|
|
136
|
+
"aws_access_key_id": aws_access_key_id,
|
|
137
|
+
"aws_secret_access_key": aws_secret_access_key
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
click.echo(f"Updated default AWS profile with {env_name} credentials")
|
|
141
|
+
|
|
142
|
+
# Update config file for both profile name and default
|
|
143
|
+
config_file = os.path.join(aws_config_dir, "config")
|
|
144
|
+
config_entries = {}
|
|
145
|
+
if aws_region:
|
|
146
|
+
config_entries["region"] = aws_region
|
|
147
|
+
|
|
148
|
+
if config_entries:
|
|
149
|
+
# Update the named profile
|
|
150
|
+
cls._update_aws_file(config_file, f"profile {env_name}", config_entries)
|
|
151
|
+
|
|
152
|
+
# Also update default profile
|
|
153
|
+
cls._update_aws_file(config_file, "default", config_entries)
|
|
154
|
+
|
|
155
|
+
# Set the AWS_PROFILE environment variable
|
|
156
|
+
os.environ["AWS_PROFILE"] = env_name
|
|
157
|
+
click.echo(f"Set AWS_PROFILE={env_name}")
|
|
158
|
+
|
|
159
|
+
click.echo(f"Set environment to {env_name}")
|
|
160
|
+
|
|
161
|
+
# Display region if available
|
|
162
|
+
if "AWS_REGION" in os.environ:
|
|
163
|
+
click.echo(f"AWS Region: {os.environ['AWS_REGION']}")
|
|
164
|
+
|
|
165
|
+
# Display profile information using AWS CLI with colored output
|
|
166
|
+
try:
|
|
167
|
+
result = subprocess.run(["aws", "sts", "get-caller-identity"], capture_output=True, text=True)
|
|
168
|
+
if result.returncode == 0:
|
|
169
|
+
formatted_json = subprocess.run(["jq", "-C", "."], input=result.stdout, capture_output=True, text=True)
|
|
170
|
+
click.echo(formatted_json.stdout)
|
|
171
|
+
else:
|
|
172
|
+
click.echo("Error fetching AWS caller identity.")
|
|
173
|
+
except Exception as e:
|
|
174
|
+
click.echo(f"Error fetching AWS identity: {e}")
|
|
175
|
+
|
|
176
|
+
except Exception as e:
|
|
177
|
+
click.echo(f"Error setting environment: {e}")
|
|
178
|
+
else:
|
|
179
|
+
click.echo(f"Environment path does not exist: {env_path}")
|
|
180
|
+
|
|
181
|
+
@staticmethod
|
|
182
|
+
def _update_aws_file(file_path, section_name, entries):
|
|
183
|
+
"""Update AWS credentials or config file with given section and entries"""
|
|
184
|
+
config_content = ""
|
|
185
|
+
section_exists = False
|
|
186
|
+
|
|
187
|
+
# Read existing content if file exists
|
|
188
|
+
if os.path.exists(file_path):
|
|
189
|
+
with open(file_path, 'r') as f:
|
|
190
|
+
config_content = f.read()
|
|
191
|
+
|
|
192
|
+
# Parse sections
|
|
193
|
+
sections = {}
|
|
194
|
+
current_section = None
|
|
195
|
+
for line in config_content.splitlines():
|
|
196
|
+
line = line.strip()
|
|
197
|
+
if line.startswith('[') and line.endswith(']'):
|
|
198
|
+
current_section = line[1:-1]
|
|
199
|
+
sections[current_section] = []
|
|
200
|
+
elif current_section is not None:
|
|
201
|
+
sections[current_section].append(line)
|
|
202
|
+
|
|
203
|
+
# Update or add section
|
|
204
|
+
sections[section_name] = [f"{key} = {value}" for key, value in entries.items()]
|
|
205
|
+
|
|
206
|
+
# Write updated content
|
|
207
|
+
with open(file_path, 'w') as f:
|
|
208
|
+
for section, lines in sections.items():
|
|
209
|
+
f.write(f"[{section}]\n")
|
|
210
|
+
for line in lines:
|
|
211
|
+
f.write(f"{line}\n")
|
|
212
|
+
f.write("\n") # Empty line between sections
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
import os
|
|
5
|
+
import subprocess
|
|
6
|
+
import re
|
|
7
|
+
from toast.plugins.base_plugin import BasePlugin
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class GitPlugin(BasePlugin):
|
|
11
|
+
"""Plugin for 'git' command - handles Git repository operations."""
|
|
12
|
+
|
|
13
|
+
name = "git"
|
|
14
|
+
help = "Manage Git repositories"
|
|
15
|
+
|
|
16
|
+
@classmethod
|
|
17
|
+
def get_arguments(cls, func):
|
|
18
|
+
func = click.argument("command", required=True)(func)
|
|
19
|
+
func = click.argument("repo_name", required=True)(func)
|
|
20
|
+
func = click.option(
|
|
21
|
+
"--target", "-t", help="Target directory name for clone operation"
|
|
22
|
+
)(func)
|
|
23
|
+
return func
|
|
24
|
+
|
|
25
|
+
@classmethod
|
|
26
|
+
def execute(cls, command, repo_name, target=None, **kwargs):
|
|
27
|
+
# Get the current path
|
|
28
|
+
current_path = os.getcwd()
|
|
29
|
+
|
|
30
|
+
# Check if the current path matches the expected pattern
|
|
31
|
+
pattern = r"^.*/workspace/github.com/([^/]+)$"
|
|
32
|
+
match = re.match(pattern, current_path)
|
|
33
|
+
|
|
34
|
+
if not match:
|
|
35
|
+
click.echo(
|
|
36
|
+
"Error: Current directory must be in ~/workspace/github.com/{username} format"
|
|
37
|
+
)
|
|
38
|
+
return
|
|
39
|
+
|
|
40
|
+
# Extract username from the path
|
|
41
|
+
username = match.group(1)
|
|
42
|
+
|
|
43
|
+
if command == "clone" or command == "cl":
|
|
44
|
+
# Determine the target directory name
|
|
45
|
+
target_dir = target if target else repo_name
|
|
46
|
+
|
|
47
|
+
# Construct the repository URL
|
|
48
|
+
repo_url = f"https://github.com/{username}/{repo_name}.git"
|
|
49
|
+
|
|
50
|
+
# Target path in the current directory
|
|
51
|
+
target_path = os.path.join(current_path, target_dir)
|
|
52
|
+
|
|
53
|
+
# Check if the target directory already exists
|
|
54
|
+
if os.path.exists(target_path):
|
|
55
|
+
click.echo(f"Error: Target directory '{target_dir}' already exists")
|
|
56
|
+
return
|
|
57
|
+
|
|
58
|
+
# Clone the repository
|
|
59
|
+
click.echo(f"Cloning {repo_url} into {target_path}...")
|
|
60
|
+
try:
|
|
61
|
+
result = subprocess.run(
|
|
62
|
+
["git", "clone", repo_url, target_path],
|
|
63
|
+
capture_output=True,
|
|
64
|
+
text=True,
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
if result.returncode == 0:
|
|
68
|
+
click.echo(f"Successfully cloned {repo_name} to {target_path}")
|
|
69
|
+
else:
|
|
70
|
+
click.echo(f"Error cloning repository: {result.stderr}")
|
|
71
|
+
except Exception as e:
|
|
72
|
+
click.echo(f"Error executing git command: {e}")
|
|
73
|
+
|
|
74
|
+
elif command == "rm":
|
|
75
|
+
# Path to the repository
|
|
76
|
+
repo_path = os.path.join(current_path, repo_name)
|
|
77
|
+
|
|
78
|
+
# Check if the repository exists
|
|
79
|
+
if not os.path.exists(repo_path):
|
|
80
|
+
click.echo(f"Error: Repository directory '{repo_name}' does not exist")
|
|
81
|
+
return
|
|
82
|
+
|
|
83
|
+
try:
|
|
84
|
+
# Remove the repository
|
|
85
|
+
subprocess.run(["rm", "-rf", repo_path], check=True)
|
|
86
|
+
click.echo(f"Successfully removed {repo_path}")
|
|
87
|
+
except Exception as e:
|
|
88
|
+
click.echo(f"Error removing repository: {e}")
|
|
89
|
+
|
|
90
|
+
else:
|
|
91
|
+
click.echo(f"Unknown command: {command}")
|
|
92
|
+
click.echo("Available commands: clone, rm")
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
import subprocess
|
|
5
|
+
from toast.plugins.base_plugin import BasePlugin
|
|
6
|
+
from toast.plugins.utils import select_from_list
|
|
7
|
+
|
|
8
|
+
class RegionPlugin(BasePlugin):
|
|
9
|
+
"""Plugin for 'region' command - sets AWS region."""
|
|
10
|
+
|
|
11
|
+
name = "region"
|
|
12
|
+
help = "Set AWS region"
|
|
13
|
+
|
|
14
|
+
@classmethod
|
|
15
|
+
def execute(cls, **kwargs):
|
|
16
|
+
try:
|
|
17
|
+
result = subprocess.run(["aws", "ec2", "describe-regions", "--query", "Regions[].RegionName", "--output", "text"], capture_output=True, text=True)
|
|
18
|
+
regions = sorted(result.stdout.split())
|
|
19
|
+
if not regions:
|
|
20
|
+
click.echo("No regions found.")
|
|
21
|
+
return
|
|
22
|
+
|
|
23
|
+
selected_region = select_from_list(regions, "Select AWS Region")
|
|
24
|
+
|
|
25
|
+
if selected_region:
|
|
26
|
+
subprocess.run(["aws", "configure", "set", "default.region", selected_region])
|
|
27
|
+
subprocess.run(["aws", "configure", "set", "default.output", "json"])
|
|
28
|
+
click.echo(f"Set AWS region to {selected_region}")
|
|
29
|
+
else:
|
|
30
|
+
click.echo("No region selected.")
|
|
31
|
+
except Exception as e:
|
|
32
|
+
click.echo(f"Error fetching AWS regions: {e}")
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
from toast.plugins.base_plugin import BasePlugin
|
|
5
|
+
|
|
6
|
+
class SsmPlugin(BasePlugin):
|
|
7
|
+
"""Plugin for 'ssm' command - runs SSM commands."""
|
|
8
|
+
|
|
9
|
+
name = "ssm"
|
|
10
|
+
help = "Run AWS SSM commands"
|
|
11
|
+
|
|
12
|
+
@classmethod
|
|
13
|
+
def execute(cls, **kwargs):
|
|
14
|
+
click.echo("Running SSM command")
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
from toast.plugins.base_plugin import BasePlugin
|
|
5
|
+
|
|
6
|
+
class UpdatePlugin(BasePlugin):
|
|
7
|
+
"""Plugin for 'update' command - updates CLI tool."""
|
|
8
|
+
|
|
9
|
+
name = "update"
|
|
10
|
+
help = "Update CLI tool"
|
|
11
|
+
|
|
12
|
+
@classmethod
|
|
13
|
+
def execute(cls, **kwargs):
|
|
14
|
+
click.echo("Updating CLI tool")
|
toast/plugins/utils.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
import subprocess
|
|
4
|
+
import click
|
|
5
|
+
|
|
6
|
+
def select_from_list(options, prompt="Select an option"):
|
|
7
|
+
try:
|
|
8
|
+
fzf_proc = subprocess.run(["fzf", "--height=15", "--reverse", "--border", "--prompt", prompt+": "], input="\n".join(options), capture_output=True, text=True)
|
|
9
|
+
return fzf_proc.stdout.strip()
|
|
10
|
+
except Exception as e:
|
|
11
|
+
click.echo(f"Error selecting from list: {e}")
|
|
12
|
+
return None
|