aws-ssh-utils 0.2.3__tar.gz → 0.2.5__tar.gz

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.
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: aws-ssh-utils
3
- Version: 0.2.3
3
+ Version: 0.2.5
4
4
  Summary: Easy AWS SSHing
5
5
  Project-URL: Homepage, https://github.com/mvanderlee/aws-ssh-utils
6
6
  Author-email: Michiel Vanderlee <jmt.vanderlee@gmail.com>
@@ -16,6 +16,7 @@ Classifier: Programming Language :: Python :: 3 :: Only
16
16
  Classifier: Programming Language :: Python :: 3.11
17
17
  Classifier: Programming Language :: Python :: 3.12
18
18
  Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
19
20
  Classifier: Topic :: Software Development
20
21
  Classifier: Topic :: Software Development :: Libraries
21
22
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
@@ -23,18 +24,14 @@ Classifier: Typing :: Typed
23
24
  Requires-Python: >=3.11
24
25
  Requires-Dist: boto3
25
26
  Requires-Dist: click
26
- Requires-Dist: click-spinner
27
- Requires-Dist: coloredlogs
28
- Requires-Dist: environs
29
27
  Requires-Dist: loguru
30
28
  Requires-Dist: paramiko
29
+ Requires-Dist: pyte>=0.8.2
31
30
  Requires-Dist: questionary
31
+ Requires-Dist: requests>=2.34.2
32
+ Requires-Dist: rich>=15.0.0
33
+ Requires-Dist: textual>=8.2.8
32
34
  Requires-Dist: typing-extensions
33
- Provides-Extra: dev
34
- Requires-Dist: boto3-stubs[ec2,emr]; extra == 'dev'
35
- Provides-Extra: publish
36
- Requires-Dist: hatch>=1.7.0; extra == 'publish'
37
- Provides-Extra: test
38
35
  Description-Content-Type: text/markdown
39
36
 
40
37
  # AWS SSH Utils
@@ -51,12 +48,41 @@ aws_ssh ec2
51
48
  aws_ssh emr
52
49
 
53
50
  aws_ssh emr-all
51
+
52
+ aws_ssh app
54
53
  ```
55
54
 
56
55
  This allows you to interactively SSH to an EC2 instance, EMR instance, or all EMR instances with TMUX.
57
56
 
58
57
  It utilizes [questionary](https://pypi.org/project/questionary/) to ask you which instance you want to connect to.
59
58
 
59
+ ## App
60
+
61
+ Terminal UI to browse EC2 instances and EMR clusters, with SSH shells in tabs.
62
+ It's the default command, so `aws_ssh` and `aws_ssh -p my-profile` launch it too.
63
+ Each connection tries, in order: `~/.ssh/config`, SSH over AWS SSM (`session-manager-plugin`), opkssh (via instance/cluster tags),
64
+ the instance's EC2 key found in `~/.ssh`, then your default keys/ssh-agent.
65
+ Unknown host keys are accepted and appended to `~/.ssh/known_hosts`; changed host keys are rejected.
66
+
67
+ ```shell
68
+ $ aws_ssh app --help
69
+ Usage: aws_ssh app [OPTIONS]
70
+
71
+ Browse EC2 instances and EMR clusters, and open SSH shells in tabs.
72
+
73
+ Options:
74
+ -p, --profile TEXT Which AWS profile to use
75
+ -r, --region TEXT Which AWS region to use
76
+ --scrollback INTEGER RANGE Lines of scrollback per shell. Defaults to your
77
+ tmux or Windows Terminal setting, else 1000.
78
+ [x>=0]
79
+ --help Show this message and exit.
80
+ ```
81
+
82
+ Scroll back with the mouse wheel or `shift+PgUp`/`shift+PgDn`; typing jumps back to the prompt.
83
+ `F12` shows/hides the sidebar, `ctrl+w` closes a failed tab, `ctrl+q` quits. Every other key goes to the focused shell.
84
+ Logs are viewable with `textual console`.
85
+
60
86
  ## EC2
61
87
 
62
88
  Select an instance from an interactive list. You can filter the instances by name.
@@ -73,7 +99,10 @@ Options:
73
99
  -r, --region TEXT Which AWS region to use
74
100
  -u, --user TEXT Which user to connect as
75
101
  --private / --public Connect to the instance's private or public IP
76
- -k, --key-file TEXT Which key file to use to connect
102
+ -k, --key-file FILE Which key file to use to connect
103
+ --scrollback INTEGER RANGE
104
+ Lines of scrollback per shell. Defaults to your tmux or
105
+ Windows Terminal setting, else 1000. [x>=0]
77
106
  --help Show this message and exit.
78
107
  ```
79
108
 
@@ -92,7 +121,10 @@ Options:
92
121
  -r, --region TEXT Which AWS region to use
93
122
  -u, --user TEXT Which user to connect as
94
123
  --private / --public Connect to the instance's private or public IP
95
- -k, --key-file TEXT Which key file to use to connect
124
+ -k, --key-file FILE Which key file to use to connect
125
+ --scrollback INTEGER RANGE
126
+ Lines of scrollback per shell. Defaults to your tmux or
127
+ Windows Terminal setting, else 1000. [x>=0]
96
128
  --help Show this message and exit.
97
129
  ```
98
130
 
@@ -113,6 +145,6 @@ Options:
113
145
  -r, --region TEXT Which AWS region to use
114
146
  -u, --user TEXT Which user to connect as
115
147
  --private / --public Connect to the instance's private or public IP
116
- -k, --key-file TEXT Which key file to use to connect
148
+ -k, --key-file FILE Which key file to use to connect
117
149
  --help Show this message and exit.
118
150
  ```
@@ -1,79 +1,114 @@
1
- # AWS SSH Utils
2
-
3
- [![Version](https://img.shields.io/pypi/v/aws-ssh-utils.svg)](https://pypi.org/project/aws-ssh-utils/)
4
- [![License](https://img.shields.io/pypi/l/aws-ssh-utils.svg)](#)
5
- [![Supported Python Versions](https://img.shields.io/pypi/pyversions/aws-ssh-utils.svg)](https://pypi.org/project/aws-ssh-utils/)
6
-
7
- ```shell
8
- pip install aws-ssh-utils
9
-
10
- aws_ssh ec2
11
-
12
- aws_ssh emr
13
-
14
- aws_ssh emr-all
15
- ```
16
-
17
- This allows you to interactively SSH to an EC2 instance, EMR instance, or all EMR instances with TMUX.
18
-
19
- It utilizes [questionary](https://pypi.org/project/questionary/) to ask you which instance you want to connect to.
20
-
21
- ## EC2
22
-
23
- Select an instance from an interactive list. You can filter the instances by name.
24
-
25
- ```shell
26
- $ aws_auth ec2 --help
27
- Usage: aws_ssh ec2 [OPTIONS]
28
-
29
- Asks user which EC2 instance they want to connect to, then opens an
30
- interactive SSH session to the instance
31
-
32
- Options:
33
- -p, --profile TEXT Which AWS profile to use
34
- -r, --region TEXT Which AWS region to use
35
- -u, --user TEXT Which user to connect as
36
- --private / --public Connect to the instance's private or public IP
37
- -k, --key-file TEXT Which key file to use to connect
38
- --help Show this message and exit.
39
- ```
40
-
41
- ## EMR
42
-
43
- Select the EMR cluster, instance group, and instance to connect to.
44
-
45
- ```shell
46
- $ aws_ssh emr --help
47
- Usage: aws_ssh emr [OPTIONS]
48
-
49
- Asks user which Cluster and EC2 instance they want to connect to, then opens an interactive SSH session to the instance
50
-
51
- Options:
52
- -p, --profile TEXT Which AWS profile to use
53
- -r, --region TEXT Which AWS region to use
54
- -u, --user TEXT Which user to connect as
55
- --private / --public Connect to the instance's private or public IP
56
- -k, --key-file TEXT Which key file to use to connect
57
- --help Show this message and exit.
58
- ```
59
-
60
- ## EMR All
61
-
62
- Select the EMR cluster to connect to. Then creates a new TMUX session with a window per instance. Each window will have an SSH connection to that instance open.
63
-
64
- ```shell
65
- $ aws_ssh emr-all --help
66
- Usage: aws_ssh emr-all [OPTIONS]
67
-
68
- Asks user which Cluster and EC2 instance they want to connect to, Then
69
- prints a tmux cli statement that will open a new session with a window per
70
- ec2 instance with ssh shell already opened.
71
-
72
- Options:
73
- -p, --profile TEXT Which AWS profile to use
74
- -r, --region TEXT Which AWS region to use
75
- -u, --user TEXT Which user to connect as
76
- --private / --public Connect to the instance's private or public IP
77
- -k, --key-file TEXT Which key file to use to connect
78
- --help Show this message and exit.
79
- ```
1
+ # AWS SSH Utils
2
+
3
+ [![Version](https://img.shields.io/pypi/v/aws-ssh-utils.svg)](https://pypi.org/project/aws-ssh-utils/)
4
+ [![License](https://img.shields.io/pypi/l/aws-ssh-utils.svg)](#)
5
+ [![Supported Python Versions](https://img.shields.io/pypi/pyversions/aws-ssh-utils.svg)](https://pypi.org/project/aws-ssh-utils/)
6
+
7
+ ```shell
8
+ pip install aws-ssh-utils
9
+
10
+ aws_ssh ec2
11
+
12
+ aws_ssh emr
13
+
14
+ aws_ssh emr-all
15
+
16
+ aws_ssh app
17
+ ```
18
+
19
+ This allows you to interactively SSH to an EC2 instance, EMR instance, or all EMR instances with TMUX.
20
+
21
+ It utilizes [questionary](https://pypi.org/project/questionary/) to ask you which instance you want to connect to.
22
+
23
+ ## App
24
+
25
+ Terminal UI to browse EC2 instances and EMR clusters, with SSH shells in tabs.
26
+ It's the default command, so `aws_ssh` and `aws_ssh -p my-profile` launch it too.
27
+ Each connection tries, in order: `~/.ssh/config`, SSH over AWS SSM (`session-manager-plugin`), opkssh (via instance/cluster tags),
28
+ the instance's EC2 key found in `~/.ssh`, then your default keys/ssh-agent.
29
+ Unknown host keys are accepted and appended to `~/.ssh/known_hosts`; changed host keys are rejected.
30
+
31
+ ```shell
32
+ $ aws_ssh app --help
33
+ Usage: aws_ssh app [OPTIONS]
34
+
35
+ Browse EC2 instances and EMR clusters, and open SSH shells in tabs.
36
+
37
+ Options:
38
+ -p, --profile TEXT Which AWS profile to use
39
+ -r, --region TEXT Which AWS region to use
40
+ --scrollback INTEGER RANGE Lines of scrollback per shell. Defaults to your
41
+ tmux or Windows Terminal setting, else 1000.
42
+ [x>=0]
43
+ --help Show this message and exit.
44
+ ```
45
+
46
+ Scroll back with the mouse wheel or `shift+PgUp`/`shift+PgDn`; typing jumps back to the prompt.
47
+ `F12` shows/hides the sidebar, `ctrl+w` closes a failed tab, `ctrl+q` quits. Every other key goes to the focused shell.
48
+ Logs are viewable with `textual console`.
49
+
50
+ ## EC2
51
+
52
+ Select an instance from an interactive list. You can filter the instances by name.
53
+
54
+ ```shell
55
+ $ aws_auth ec2 --help
56
+ Usage: aws_ssh ec2 [OPTIONS]
57
+
58
+ Asks user which EC2 instance they want to connect to, then opens an
59
+ interactive SSH session to the instance
60
+
61
+ Options:
62
+ -p, --profile TEXT Which AWS profile to use
63
+ -r, --region TEXT Which AWS region to use
64
+ -u, --user TEXT Which user to connect as
65
+ --private / --public Connect to the instance's private or public IP
66
+ -k, --key-file FILE Which key file to use to connect
67
+ --scrollback INTEGER RANGE
68
+ Lines of scrollback per shell. Defaults to your tmux or
69
+ Windows Terminal setting, else 1000. [x>=0]
70
+ --help Show this message and exit.
71
+ ```
72
+
73
+ ## EMR
74
+
75
+ Select the EMR cluster, instance group, and instance to connect to.
76
+
77
+ ```shell
78
+ $ aws_ssh emr --help
79
+ Usage: aws_ssh emr [OPTIONS]
80
+
81
+ Asks user which Cluster and EC2 instance they want to connect to, then opens an interactive SSH session to the instance
82
+
83
+ Options:
84
+ -p, --profile TEXT Which AWS profile to use
85
+ -r, --region TEXT Which AWS region to use
86
+ -u, --user TEXT Which user to connect as
87
+ --private / --public Connect to the instance's private or public IP
88
+ -k, --key-file FILE Which key file to use to connect
89
+ --scrollback INTEGER RANGE
90
+ Lines of scrollback per shell. Defaults to your tmux or
91
+ Windows Terminal setting, else 1000. [x>=0]
92
+ --help Show this message and exit.
93
+ ```
94
+
95
+ ## EMR All
96
+
97
+ Select the EMR cluster to connect to. Then creates a new TMUX session with a window per instance. Each window will have an SSH connection to that instance open.
98
+
99
+ ```shell
100
+ $ aws_ssh emr-all --help
101
+ Usage: aws_ssh emr-all [OPTIONS]
102
+
103
+ Asks user which Cluster and EC2 instance they want to connect to, Then
104
+ prints a tmux cli statement that will open a new session with a window per
105
+ ec2 instance with ssh shell already opened.
106
+
107
+ Options:
108
+ -p, --profile TEXT Which AWS profile to use
109
+ -r, --region TEXT Which AWS region to use
110
+ -u, --user TEXT Which user to connect as
111
+ --private / --public Connect to the instance's private or public IP
112
+ -k, --key-file FILE Which key file to use to connect
113
+ --help Show this message and exit.
114
+ ```
@@ -0,0 +1 @@
1
+ __version__ = "0.2.5"
@@ -0,0 +1,165 @@
1
+ """Shared helpers for the interactive (questionary based) commands."""
2
+
3
+ import ctypes
4
+ import functools
5
+ import json
6
+ import os
7
+ import subprocess
8
+ import sys
9
+ from collections.abc import Callable
10
+ from pathlib import Path
11
+ from shutil import which
12
+ from typing import ParamSpec, TypeVar
13
+
14
+ import click
15
+ import paramiko
16
+ import questionary
17
+ from botocore.exceptions import ClientError
18
+ from loguru import logger
19
+ from rich.console import Console
20
+ from rich.markup import escape
21
+ from rich.status import Status
22
+
23
+ from aws_ssh_utils.commands.healthcheck import is_aws_ssm_installed, is_opkssh_installed
24
+ from aws_ssh_utils.connection import Environment, Target, connect, open_shell_channel
25
+ from aws_ssh_utils.terminal import ShellApp
26
+
27
+ P = ParamSpec('P')
28
+ R = TypeVar('R')
29
+
30
+ DEFAULT_SCROLLBACK = 1000
31
+ WINDOWS_TERMINAL_DEFAULT_SCROLLBACK = 9001
32
+ WINDOWS_TERMINAL_SETTINGS = (
33
+ 'Packages/Microsoft.WindowsTerminal_8wekyb3d8bbwe/LocalState/settings.json',
34
+ 'Packages/Microsoft.WindowsTerminalPreview_8wekyb3d8bbwe/LocalState/settings.json',
35
+ 'Microsoft/Windows Terminal/settings.json',
36
+ )
37
+ """Relative to %LOCALAPPDATA%: Store, Store preview, and unpackaged installs."""
38
+
39
+ console = Console(stderr=True)
40
+
41
+
42
+ class ShellError(Exception):
43
+ def __init__(self, message: str, exit_code: int = 1):
44
+ super().__init__(message, exit_code)
45
+ self.message: str = message
46
+ self.exit_code: int = exit_code
47
+
48
+
49
+ def spinner(message: str = '') -> Status:
50
+ return console.status(message, spinner='dots')
51
+
52
+
53
+ def handle_errors(func: Callable[P, R]) -> Callable[P, R]:
54
+ """Log ShellErrors and AWS errors, and exit, instead of printing a traceback."""
55
+
56
+ @functools.wraps(func)
57
+ def wrapper(*args: P.args, **kwargs: P.kwargs) -> R:
58
+ try:
59
+ return func(*args, **kwargs)
60
+ except ShellError as e:
61
+ logger.error(e.message)
62
+ sys.exit(e.exit_code)
63
+ except ClientError as e:
64
+ if e.response.get('Error', {}).get('Code') == 'ExpiredTokenException':
65
+ logger.critical('Your AWS Token has expired. Please update and try again.')
66
+ else:
67
+ logger.error(e)
68
+ sys.exit(1)
69
+
70
+ return wrapper
71
+
72
+
73
+ def set_terminal_title(title: str = ''):
74
+ if os.name == 'nt':
75
+ ctypes.windll.kernel32.SetConsoleTitleW(title) # pyright: ignore[reportAttributeAccessIssue]
76
+ elif os.getenv('TMUX'):
77
+ sys.stdout.write(f"\33k{title}\33")
78
+ sys.stdout.flush()
79
+ else:
80
+ sys.stdout.write(f"\33]0;{title}\a")
81
+ sys.stdout.flush()
82
+
83
+
84
+ def detect_environment(profile: str | None, region: str | None) -> Environment:
85
+ return Environment(
86
+ ssm_installed=is_aws_ssm_installed() and which('aws') is not None,
87
+ opkssh_installed=is_opkssh_installed(),
88
+ profile=profile,
89
+ region=region,
90
+ )
91
+
92
+
93
+ def detect_scrollback() -> int:
94
+ """The scrollback of the terminal we run in, when it can be read (tmux, Windows Terminal)."""
95
+ if os.getenv('TMUX'):
96
+ try:
97
+ result = subprocess.run(
98
+ ['tmux', 'show-options', '-gv', 'history-limit'], # noqa: S607
99
+ capture_output=True,
100
+ text=True,
101
+ check=True,
102
+ timeout=5,
103
+ )
104
+ return int(result.stdout)
105
+ except (OSError, subprocess.SubprocessError, ValueError) as e:
106
+ logger.debug(f"Could not read tmux history-limit: {e}")
107
+ if os.getenv('WT_SESSION'):
108
+ return windows_terminal_history_size()
109
+ return DEFAULT_SCROLLBACK
110
+
111
+
112
+ def windows_terminal_history_size() -> int:
113
+ """`historySize` of the current Windows Terminal profile, falling back to the profile defaults."""
114
+ local_app_data = Path(os.getenv('LOCALAPPDATA', ''))
115
+ for settings_file in WINDOWS_TERMINAL_SETTINGS:
116
+ try:
117
+ settings = json.loads((local_app_data / settings_file).read_text(encoding='utf-8'))
118
+ except (OSError, ValueError):
119
+ continue
120
+ profiles = settings.get('profiles', {})
121
+ # Older settings files have a plain list of profiles
122
+ profile_list = profiles.get('list', []) if isinstance(profiles, dict) else profiles
123
+ defaults = profiles.get('defaults', {}) if isinstance(profiles, dict) else {}
124
+ profile_id = os.getenv('WT_PROFILE_ID', '').lower()
125
+ profile = next((p for p in profile_list if p.get('guid', '').lower() == profile_id), {})
126
+ return int(profile.get('historySize', defaults.get('historySize', WINDOWS_TERMINAL_DEFAULT_SCROLLBACK)))
127
+ return WINDOWS_TERMINAL_DEFAULT_SCROLLBACK
128
+
129
+
130
+ scrollback_option = click.option(
131
+ '--scrollback',
132
+ type=click.IntRange(min=0),
133
+ default=None,
134
+ help="Lines of scrollback per shell. Defaults to your tmux or Windows Terminal setting, else 1000.",
135
+ )
136
+
137
+
138
+ def confirm_host_key(hostname: str, key: paramiko.PKey) -> bool:
139
+ logger.warning(f"Unknown {key.get_name()} host key for {hostname}: {key.fingerprint}")
140
+ return questionary.confirm("Continue and add host key?").unsafe_ask()
141
+
142
+
143
+ def open_shell(target: Target, env: Environment, title: str, scrollback: int | None, initial_input: bytes = b''):
144
+ """Connect to the target, then show the shell full-screen until it closes.
145
+
146
+ `scrollback` defaults to the terminal's own setting, see `detect_scrollback`.
147
+ """
148
+ result = connect(target, env, on_output=logger.info, on_waiting=lambda: None, confirm_host_key=confirm_host_key)
149
+ if isinstance(result, list):
150
+ for line in result:
151
+ console.print(escape(line))
152
+ raise ShellError(f'Failed to connect to {target.user}@{target.ip}')
153
+
154
+ channel = open_shell_channel(result.client)
155
+ if initial_input:
156
+ channel.send(initial_input)
157
+
158
+ set_terminal_title(title)
159
+ # Logs, e.g. AWS SSM's stderr, would draw over the full-screen shell.
160
+ logger.disable('aws_ssh_utils')
161
+ try:
162
+ ShellApp(result.via, result.client, channel, detect_scrollback() if scrollback is None else scrollback).run()
163
+ finally:
164
+ logger.enable('aws_ssh_utils')
165
+ set_terminal_title()