nate2-scripts 1.0.0__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.
- nate2_scripts/__init__.py +4 -0
- nate2_scripts/bing_wrapper.py +170 -0
- nate2_scripts/console/__init__.py +174 -0
- nate2_scripts/console/arnResolver.py +514 -0
- nate2_scripts/console/destitationResolver.py +104 -0
- nate2_scripts/console/hashGrabber.py +178 -0
- nate2_scripts/console/regionCache.py +50 -0
- nate2_scripts/console/resourceIdResolver.py +174 -0
- nate2_scripts/console/sessionCache.py +96 -0
- nate2_scripts/test.py +98 -0
- nate2_scripts-1.0.0.dist-info/METADATA +9 -0
- nate2_scripts-1.0.0.dist-info/RECORD +15 -0
- nate2_scripts-1.0.0.dist-info/WHEEL +4 -0
- nate2_scripts-1.0.0.dist-info/entry_points.txt +3 -0
- nate2_scripts-1.0.0.dist-info/licenses/LICENSE +674 -0
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"""PTY wrapper for cdk that plays a sound when (y/n) prompts appear."""
|
|
2
|
+
|
|
3
|
+
import fcntl
|
|
4
|
+
import os
|
|
5
|
+
import select
|
|
6
|
+
import signal
|
|
7
|
+
import struct
|
|
8
|
+
import subprocess
|
|
9
|
+
import sys
|
|
10
|
+
import termios
|
|
11
|
+
import tty
|
|
12
|
+
import argparse
|
|
13
|
+
|
|
14
|
+
# _PATTERN = b"(y/n)"
|
|
15
|
+
# _WINDOW = len(_PATTERN) * 2
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _get_winsize(fd: int) -> bytes:
|
|
19
|
+
return fcntl.ioctl(fd, termios.TIOCGWINSZ, b"\x00" * 8)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _set_winsize(fd: int, winsize: bytes) -> None:
|
|
23
|
+
fcntl.ioctl(fd, termios.TIOCSWINSZ, winsize)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def run(args: argparse.Namespace) -> None:
|
|
27
|
+
patterns = [p.encode() for p in args.patterns] if args.patterns else []
|
|
28
|
+
window = max(map(len, patterns)) * 2 if patterns else 1
|
|
29
|
+
# Capture real terminal dimensions before creating the PTY.
|
|
30
|
+
# pty.spawn() leaves the PTY at {0,0,0,0}, which makes CDK format
|
|
31
|
+
# output to width 1. Using os.openpty() lets us set the size before
|
|
32
|
+
# forking so cdk reads correct dimensions from its very first TIOCGWINSZ.
|
|
33
|
+
try:
|
|
34
|
+
winsize = _get_winsize(sys.stdout.fileno())
|
|
35
|
+
except OSError:
|
|
36
|
+
winsize = struct.pack("HHHH", 24, 80, 0, 0)
|
|
37
|
+
|
|
38
|
+
master_fd, slave_fd = os.openpty()
|
|
39
|
+
_set_winsize(master_fd, winsize)
|
|
40
|
+
|
|
41
|
+
pid = os.fork()
|
|
42
|
+
if pid == 0:
|
|
43
|
+
# ---- child ----
|
|
44
|
+
os.close(master_fd)
|
|
45
|
+
os.setsid()
|
|
46
|
+
# Acquire slave as the controlling terminal.
|
|
47
|
+
fcntl.ioctl(slave_fd, termios.TIOCSCTTY, 0)
|
|
48
|
+
for fd in (0, 1, 2):
|
|
49
|
+
os.dup2(slave_fd, fd)
|
|
50
|
+
if slave_fd > 2:
|
|
51
|
+
os.close(slave_fd)
|
|
52
|
+
os.execvp(args.command[0], args.command)
|
|
53
|
+
os._exit(1)
|
|
54
|
+
|
|
55
|
+
# ---- parent ----
|
|
56
|
+
os.close(slave_fd)
|
|
57
|
+
|
|
58
|
+
# Propagate terminal resizes to the child (also sends SIGWINCH to it).
|
|
59
|
+
def _sigwinch(signum: int, frame) -> None:
|
|
60
|
+
try:
|
|
61
|
+
_set_winsize(master_fd, _get_winsize(sys.stdout.fileno()))
|
|
62
|
+
except OSError:
|
|
63
|
+
pass
|
|
64
|
+
|
|
65
|
+
signal.signal(signal.SIGWINCH, _sigwinch)
|
|
66
|
+
|
|
67
|
+
stdin_fd = sys.stdin.fileno()
|
|
68
|
+
old_settings = None
|
|
69
|
+
try:
|
|
70
|
+
old_settings = termios.tcgetattr(stdin_fd)
|
|
71
|
+
tty.setraw(stdin_fd)
|
|
72
|
+
except termios.error:
|
|
73
|
+
pass
|
|
74
|
+
|
|
75
|
+
buf = b""
|
|
76
|
+
try:
|
|
77
|
+
while True:
|
|
78
|
+
try:
|
|
79
|
+
r, _, _ = select.select([master_fd, stdin_fd], [], [])
|
|
80
|
+
except (OSError, ValueError):
|
|
81
|
+
break
|
|
82
|
+
|
|
83
|
+
if master_fd in r:
|
|
84
|
+
try:
|
|
85
|
+
data = os.read(master_fd, 1024)
|
|
86
|
+
except OSError:
|
|
87
|
+
break
|
|
88
|
+
if not data:
|
|
89
|
+
break
|
|
90
|
+
|
|
91
|
+
check = buf + data
|
|
92
|
+
for pattern in patterns:
|
|
93
|
+
if pattern in check:
|
|
94
|
+
subprocess.Popen(["afplay", args.sound_pattern])
|
|
95
|
+
buf = b""
|
|
96
|
+
break
|
|
97
|
+
|
|
98
|
+
buf = check[-window:]
|
|
99
|
+
|
|
100
|
+
sys.stdout.buffer.write(data)
|
|
101
|
+
sys.stdout.buffer.flush()
|
|
102
|
+
|
|
103
|
+
if stdin_fd in r:
|
|
104
|
+
try:
|
|
105
|
+
data = os.read(stdin_fd, 1024)
|
|
106
|
+
except OSError:
|
|
107
|
+
break
|
|
108
|
+
if data:
|
|
109
|
+
try:
|
|
110
|
+
os.write(master_fd, data)
|
|
111
|
+
except OSError:
|
|
112
|
+
break
|
|
113
|
+
finally:
|
|
114
|
+
if old_settings is not None:
|
|
115
|
+
try:
|
|
116
|
+
termios.tcsetattr(stdin_fd, termios.TCSADRAIN, old_settings)
|
|
117
|
+
except termios.error:
|
|
118
|
+
pass
|
|
119
|
+
|
|
120
|
+
_, status = os.waitpid(pid, 0)
|
|
121
|
+
|
|
122
|
+
if args.end:
|
|
123
|
+
subprocess.Popen(["afplay", args.sound_end])
|
|
124
|
+
if os.WIFEXITED(status):
|
|
125
|
+
sys.exit(os.WEXITSTATUS(status))
|
|
126
|
+
elif os.WIFSIGNALED(status):
|
|
127
|
+
sys.exit(128 + os.WTERMSIG(status))
|
|
128
|
+
else:
|
|
129
|
+
sys.exit(1)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def main() -> None:
|
|
133
|
+
parser = argparse.ArgumentParser(
|
|
134
|
+
description="PTY wrapper for cdk that plays a sound when (y/n) prompts appear."
|
|
135
|
+
)
|
|
136
|
+
parser.add_argument("command", nargs="*", help="Command to run")
|
|
137
|
+
parser.add_argument(
|
|
138
|
+
"--end", help="make sound at end of command", action="store_true"
|
|
139
|
+
)
|
|
140
|
+
parser.add_argument(
|
|
141
|
+
"--pattern",
|
|
142
|
+
help="make sound when pattern in seen",
|
|
143
|
+
type=str,
|
|
144
|
+
action="append",
|
|
145
|
+
dest="patterns",
|
|
146
|
+
)
|
|
147
|
+
parser.add_argument(
|
|
148
|
+
"--sound-end",
|
|
149
|
+
help="change the sound at end of command",
|
|
150
|
+
type=str,
|
|
151
|
+
default="/System/Library/Sounds/Glass.aiff",
|
|
152
|
+
)
|
|
153
|
+
parser.add_argument(
|
|
154
|
+
"--sound-pattern",
|
|
155
|
+
help="change the sound when pattern is seen",
|
|
156
|
+
type=str,
|
|
157
|
+
default="/System/Library/Sounds/Glass.aiff",
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
args = parser.parse_args()
|
|
161
|
+
|
|
162
|
+
if not args.command:
|
|
163
|
+
parser.print_help()
|
|
164
|
+
sys.exit(1)
|
|
165
|
+
|
|
166
|
+
run(args)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
if __name__ == "__main__":
|
|
170
|
+
main()
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# PYTHON_ARGCOMPLETE_OK
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import re
|
|
6
|
+
import subprocess
|
|
7
|
+
import sys
|
|
8
|
+
import time
|
|
9
|
+
import urllib.parse
|
|
10
|
+
import webbrowser
|
|
11
|
+
import argparse
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
import argcomplete
|
|
16
|
+
import boto3
|
|
17
|
+
|
|
18
|
+
from nate2_scripts.console.destitationResolver import DestinationResolver
|
|
19
|
+
|
|
20
|
+
from nate2_scripts.console.hashGrabber import HashGrabber
|
|
21
|
+
from nate2_scripts.console.regionCache import RegionCache
|
|
22
|
+
from nate2_scripts.console.sessionCache import SessionCache
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def get_account_id() -> str:
|
|
26
|
+
return boto3.client("sts").get_caller_identity()["Account"]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def get_credentials() -> dict:
|
|
30
|
+
result = subprocess.run(
|
|
31
|
+
["aws", "configure", "export-credentials"],
|
|
32
|
+
capture_output=True,
|
|
33
|
+
text=True,
|
|
34
|
+
)
|
|
35
|
+
if result.returncode != 0:
|
|
36
|
+
print(f"Error: Failed to export credentials.\n{result.stderr}", file=sys.stderr)
|
|
37
|
+
sys.exit(1)
|
|
38
|
+
return json.loads(result.stdout)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def get_signin_token(creds: dict) -> str:
|
|
42
|
+
session = json.dumps(
|
|
43
|
+
{
|
|
44
|
+
"sessionId": creds["AccessKeyId"],
|
|
45
|
+
"sessionKey": creds["SecretAccessKey"],
|
|
46
|
+
"sessionToken": creds["SessionToken"],
|
|
47
|
+
}
|
|
48
|
+
)
|
|
49
|
+
sess = urllib.parse.quote(session)
|
|
50
|
+
token_url = (
|
|
51
|
+
f"https://signin.aws.amazon.com/federation?Action=getSigninToken&Session={sess}"
|
|
52
|
+
)
|
|
53
|
+
result = subprocess.run(
|
|
54
|
+
["curl", "-s", token_url], capture_output=True, text=True, check=True
|
|
55
|
+
)
|
|
56
|
+
try:
|
|
57
|
+
return json.loads(result.stdout)["SigninToken"]
|
|
58
|
+
except json.JSONDecodeError:
|
|
59
|
+
print("Error: Failed to parse JSON response. You may not have credentials.")
|
|
60
|
+
sys.exit(1)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def build_console_url(destination: str, signin_token: str) -> str:
|
|
64
|
+
dest = urllib.parse.quote(destination, safe="")
|
|
65
|
+
return f"https://signin.aws.amazon.com/federation?Action=login&Issuer=&Destination={dest}&SigninToken={signin_token}"
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def get_profile() -> str:
|
|
69
|
+
profile_name = os.environ.get("AWS_PROFILE", "")
|
|
70
|
+
if not profile_name:
|
|
71
|
+
print(
|
|
72
|
+
"Error: AWS_PROFILE environment variable is not set. run asp.",
|
|
73
|
+
file=sys.stderr,
|
|
74
|
+
)
|
|
75
|
+
sys.exit(1)
|
|
76
|
+
return profile_name
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def parse_args(region_cache: RegionCache) -> argparse.Namespace:
|
|
80
|
+
parser = argparse.ArgumentParser(
|
|
81
|
+
description="Open AWS console for a specific service."
|
|
82
|
+
)
|
|
83
|
+
svc_arg = parser.add_argument(
|
|
84
|
+
"service",
|
|
85
|
+
type=str,
|
|
86
|
+
default="",
|
|
87
|
+
nargs="?",
|
|
88
|
+
help="The AWS service to open in the console, or an AWS ARN.",
|
|
89
|
+
)
|
|
90
|
+
setattr(
|
|
91
|
+
svc_arg,
|
|
92
|
+
"completer",
|
|
93
|
+
argcomplete.completers.ChoicesCompleter(
|
|
94
|
+
list(DestinationResolver.get_service_list())
|
|
95
|
+
),
|
|
96
|
+
)
|
|
97
|
+
parser.add_argument(
|
|
98
|
+
"-r",
|
|
99
|
+
"--region",
|
|
100
|
+
type=str,
|
|
101
|
+
default="",
|
|
102
|
+
choices=region_cache.get_regions(),
|
|
103
|
+
help="The AWS region to use.",
|
|
104
|
+
)
|
|
105
|
+
parser.add_argument(
|
|
106
|
+
"-f",
|
|
107
|
+
"--force",
|
|
108
|
+
action="store_true",
|
|
109
|
+
help="Force new federations. This will reset the federations cache.",
|
|
110
|
+
)
|
|
111
|
+
argcomplete.autocomplete(parser)
|
|
112
|
+
args = parser.parse_args()
|
|
113
|
+
|
|
114
|
+
return args
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def main() -> None:
|
|
118
|
+
profile_name = get_profile()
|
|
119
|
+
region_cache = RegionCache()
|
|
120
|
+
session_cache = SessionCache()
|
|
121
|
+
|
|
122
|
+
print("Loading regions from cache:", region_cache.get_regions())
|
|
123
|
+
print("Loading default region from cache:", region_cache.get_default_region())
|
|
124
|
+
|
|
125
|
+
args = parse_args(region_cache)
|
|
126
|
+
|
|
127
|
+
if args.force:
|
|
128
|
+
print("Force passed. Reseting session cache.")
|
|
129
|
+
session_cache.reset()
|
|
130
|
+
|
|
131
|
+
if args.region:
|
|
132
|
+
print("Default region is now:", args.region)
|
|
133
|
+
|
|
134
|
+
creds = get_credentials()
|
|
135
|
+
account_id = get_account_id()
|
|
136
|
+
hash = session_cache.get_session_hash(profile_name, creds)
|
|
137
|
+
|
|
138
|
+
multisession_domain_name = ""
|
|
139
|
+
signin_token = None
|
|
140
|
+
if hash is None:
|
|
141
|
+
print(f"{profile_name} has no cached session. Grabbing signin token.")
|
|
142
|
+
signin_token = get_signin_token(creds)
|
|
143
|
+
elif len(hash) == 0:
|
|
144
|
+
print(f"{profile_name} cache is single session")
|
|
145
|
+
else:
|
|
146
|
+
multisession_domain_name = f"{account_id}-{hash}."
|
|
147
|
+
print(
|
|
148
|
+
f"{profile_name} cache is multi session with domain: {multisession_domain_name}"
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
resolver = DestinationResolver(account_id, region_cache, multisession_domain_name)
|
|
152
|
+
|
|
153
|
+
destination = resolver.parse_destination(args.service, args.region)
|
|
154
|
+
|
|
155
|
+
print("parsed destination:", destination)
|
|
156
|
+
|
|
157
|
+
if signin_token is not None:
|
|
158
|
+
print("Building signin url")
|
|
159
|
+
console_url = build_console_url(destination, signin_token)
|
|
160
|
+
webbrowser.open(console_url)
|
|
161
|
+
start_time = time.time()
|
|
162
|
+
print(
|
|
163
|
+
"Waiting for up to 10 seconds to allow the browser to open before exiting..."
|
|
164
|
+
)
|
|
165
|
+
hash = HashGrabber.get_hash(account_id, start_time)
|
|
166
|
+
print(f"Hash found: {hash}")
|
|
167
|
+
session_cache.record_valid_hash(profile_name, hash)
|
|
168
|
+
else:
|
|
169
|
+
print("Opening url")
|
|
170
|
+
webbrowser.open(destination)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
if __name__ == "__main__":
|
|
174
|
+
main()
|