nate2-scripts 1.0.1__tar.gz → 1.0.3__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.
- {nate2_scripts-1.0.1 → nate2_scripts-1.0.3}/PKG-INFO +2 -2
- {nate2_scripts-1.0.1 → nate2_scripts-1.0.3}/README.md +14 -11
- {nate2_scripts-1.0.1 → nate2_scripts-1.0.3}/pyproject.toml +4 -1
- nate2_scripts-1.0.1/src/nate2_scripts/bing_wrapper.py → nate2_scripts-1.0.3/src/nate2_scripts/bing_wrapper/__init__.py +41 -0
- nate2_scripts-1.0.3/src/nate2_scripts/bing_wrapper/autocomplete.sh +30 -0
- {nate2_scripts-1.0.1 → nate2_scripts-1.0.3}/src/nate2_scripts/console/__init__.py +51 -14
- {nate2_scripts-1.0.1 → nate2_scripts-1.0.3}/src/nate2_scripts/console/destitationResolver.py +3 -0
- {nate2_scripts-1.0.1 → nate2_scripts-1.0.3}/src/nate2_scripts/console/nameResolver.py +12 -5
- {nate2_scripts-1.0.1 → nate2_scripts-1.0.3}/src/nate2_scripts/console/resourceIdResolver.py +10 -8
- {nate2_scripts-1.0.1 → nate2_scripts-1.0.3}/src/nate2_scripts/console/sessionCache.py +5 -1
- {nate2_scripts-1.0.1 → nate2_scripts-1.0.3}/.github/workflows/publish.yml +0 -0
- {nate2_scripts-1.0.1 → nate2_scripts-1.0.3}/.gitignore +0 -0
- {nate2_scripts-1.0.1 → nate2_scripts-1.0.3}/CONSOLE-NOTES.md +0 -0
- {nate2_scripts-1.0.1 → nate2_scripts-1.0.3}/LICENSE +0 -0
- {nate2_scripts-1.0.1 → nate2_scripts-1.0.3}/src/nate2_scripts/__init__.py +0 -0
- {nate2_scripts-1.0.1 → nate2_scripts-1.0.3}/src/nate2_scripts/console/arnResolver.py +0 -0
- {nate2_scripts-1.0.1 → nate2_scripts-1.0.3}/src/nate2_scripts/console/hashGrabber.py +0 -0
- {nate2_scripts-1.0.1 → nate2_scripts-1.0.3}/src/nate2_scripts/console/regionCache.py +0 -0
- {nate2_scripts-1.0.1 → nate2_scripts-1.0.3}/src/nate2_scripts/test.py +0 -0
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
[](https://pypi.org/project/nate2-scripts/)
|
|
3
2
|
[](https://github.com/tennex-nwestfall/nate2-scripts/blob/main/LICENSE)
|
|
4
3
|
|
|
@@ -35,7 +34,7 @@ eval "$(register-python-argcomplete console)"
|
|
|
35
34
|
**Usage:**
|
|
36
35
|
|
|
37
36
|
```bash
|
|
38
|
-
# change to an account/role
|
|
37
|
+
# change to an account/role
|
|
39
38
|
asp my-aws-role
|
|
40
39
|
|
|
41
40
|
# open main page of the role/account
|
|
@@ -46,8 +45,8 @@ console ec2
|
|
|
46
45
|
console s3
|
|
47
46
|
# open lambda page of the role/account in us-west-2
|
|
48
47
|
console lambda -r us-west-2
|
|
49
|
-
# open instance id page of the role/account in
|
|
50
|
-
# whatever region that instance is in
|
|
48
|
+
# open instance id page of the role/account in
|
|
49
|
+
# whatever region that instance is in
|
|
51
50
|
console i-0123456789abcdef
|
|
52
51
|
# open the console page for the resource the ARN points to
|
|
53
52
|
console arn:aws:ec2:us-east-1:012345678967:instance/i-0123456789abcdef
|
|
@@ -60,7 +59,7 @@ console arn:aws:ec2:us-east-1:012345678967:instance/i-0123456789abcdef
|
|
|
60
59
|
**Supported ARNs:** `ec2`, `iam` (role, user), `lambda`, `logs` (CloudWatch Logs), `rds`, `s3`, `secretsmanager`, `ecs`, `eks`, `sns`, `sqs`, `batch`, `dynamodb`, `states` (Step Functions), `elasticloadbalancing`
|
|
61
60
|
|
|
62
61
|
**All Regions Supported:**
|
|
63
|
-
Edit
|
|
62
|
+
Edit `~/.local/state/console-regions.json` to change which regions are evaluated and to set the default one
|
|
64
63
|
|
|
65
64
|
---
|
|
66
65
|
|
|
@@ -78,13 +77,17 @@ alias cdk="bing-wrapper.py --end --pattern \"(y/n)\" --sound-end /System/Library
|
|
|
78
77
|
|
|
79
78
|
# wraps nextflow to play a sound when done
|
|
80
79
|
alias nextflow="bing-wrapper.py --end -- nextflow"
|
|
80
|
+
|
|
81
|
+
# needed for autocomplete of bing-wrapper and the inner command
|
|
82
|
+
eval "$(bing-wrapper --autocomplete)"
|
|
81
83
|
```
|
|
82
84
|
|
|
83
85
|
**Options:**
|
|
84
86
|
|
|
85
|
-
| Flag
|
|
86
|
-
|
|
|
87
|
-
| `--end`
|
|
88
|
-
| `--pattern <text>`
|
|
89
|
-
| `--sound-end <path>`
|
|
90
|
-
| `--sound-pattern <path>` | Sound file to play on pattern match
|
|
87
|
+
| Flag | Description | Default |
|
|
88
|
+
| ------------------------ | --------------------------------------------------------- | ------------ |
|
|
89
|
+
| `--end` | Play a sound when the command exits | off |
|
|
90
|
+
| `--pattern <text>` | Play a sound when `<text>` appears in output (repeatable) | none |
|
|
91
|
+
| `--sound-end <path>` | Sound file to play on exit | `Glass.aiff` |
|
|
92
|
+
| `--sound-pattern <path>` | Sound file to play on pattern match | `Glass.aiff` |
|
|
93
|
+
| `--sound-while <path>` | Sound file to play on while the command runs | none |
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "nate2-scripts"
|
|
7
|
-
version = "1.0.
|
|
7
|
+
version = "1.0.3"
|
|
8
8
|
description = "Utility scripts for AWS development on macOS"
|
|
9
9
|
requires-python = ">=3.11"
|
|
10
10
|
dependencies = [
|
|
@@ -19,5 +19,8 @@ bing-wrapper = "nate2_scripts.bing_wrapper:main"
|
|
|
19
19
|
[tool.hatch.build.targets.wheel]
|
|
20
20
|
packages = ["src/nate2_scripts"]
|
|
21
21
|
|
|
22
|
+
[tool.hatch.build.targets.wheel.force-include]
|
|
23
|
+
"src/nate2_scripts/bing_wrapper/autocomplete.sh" = "nate2_scripts/bing_wrapper/autocomplete.sh"
|
|
24
|
+
|
|
22
25
|
[project.urls]
|
|
23
26
|
Homepage = "https://github.com/tennex-nwestfall/nate2-scripts"
|
|
@@ -8,6 +8,7 @@ import struct
|
|
|
8
8
|
import subprocess
|
|
9
9
|
import sys
|
|
10
10
|
import termios
|
|
11
|
+
import threading
|
|
11
12
|
import tty
|
|
12
13
|
import argparse
|
|
13
14
|
|
|
@@ -55,6 +56,26 @@ def run(args: argparse.Namespace) -> None:
|
|
|
55
56
|
# ---- parent ----
|
|
56
57
|
os.close(slave_fd)
|
|
57
58
|
|
|
59
|
+
# Loop sound-while for the lifetime of the child process.
|
|
60
|
+
_stop_sound_while = threading.Event()
|
|
61
|
+
_sound_while_thread = None
|
|
62
|
+
|
|
63
|
+
def _play_sound_while_loop() -> None:
|
|
64
|
+
while not _stop_sound_while.is_set():
|
|
65
|
+
proc = subprocess.Popen(["afplay", args.sound_while])
|
|
66
|
+
while proc.poll() is None:
|
|
67
|
+
if _stop_sound_while.wait(timeout=0.05):
|
|
68
|
+
proc.terminate()
|
|
69
|
+
try:
|
|
70
|
+
proc.wait(timeout=1)
|
|
71
|
+
except subprocess.TimeoutExpired:
|
|
72
|
+
proc.kill()
|
|
73
|
+
return
|
|
74
|
+
|
|
75
|
+
if args.sound_while:
|
|
76
|
+
_sound_while_thread = threading.Thread(target=_play_sound_while_loop, daemon=True)
|
|
77
|
+
_sound_while_thread.start()
|
|
78
|
+
|
|
58
79
|
# Propagate terminal resizes to the child (also sends SIGWINCH to it).
|
|
59
80
|
def _sigwinch(signum: int, frame) -> None:
|
|
60
81
|
try:
|
|
@@ -119,6 +140,10 @@ def run(args: argparse.Namespace) -> None:
|
|
|
119
140
|
|
|
120
141
|
_, status = os.waitpid(pid, 0)
|
|
121
142
|
|
|
143
|
+
_stop_sound_while.set()
|
|
144
|
+
if _sound_while_thread is not None:
|
|
145
|
+
_sound_while_thread.join(timeout=2)
|
|
146
|
+
|
|
122
147
|
if args.end:
|
|
123
148
|
subprocess.Popen(["afplay", args.sound_end])
|
|
124
149
|
if os.WIFEXITED(status):
|
|
@@ -156,9 +181,25 @@ def main() -> None:
|
|
|
156
181
|
type=str,
|
|
157
182
|
default="/System/Library/Sounds/Glass.aiff",
|
|
158
183
|
)
|
|
184
|
+
parser.add_argument(
|
|
185
|
+
"--sound-while",
|
|
186
|
+
help="play sound while command is running",
|
|
187
|
+
type=str,
|
|
188
|
+
default="",
|
|
189
|
+
)
|
|
190
|
+
parser.add_argument(
|
|
191
|
+
"--autocomplete",
|
|
192
|
+
help="return autocomplete scripts",
|
|
193
|
+
action="store_true",
|
|
194
|
+
)
|
|
159
195
|
|
|
160
196
|
args = parser.parse_args()
|
|
161
197
|
|
|
198
|
+
if args.autocomplete:
|
|
199
|
+
script = os.path.join(os.path.dirname(__file__), "autocomplete.sh")
|
|
200
|
+
print(open(script).read())
|
|
201
|
+
sys.exit(0)
|
|
202
|
+
|
|
162
203
|
if not args.command:
|
|
163
204
|
parser.print_help()
|
|
164
205
|
sys.exit(1)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
_bing_wrapper() {
|
|
2
|
+
local -a bing_args
|
|
3
|
+
local -i double_dash_pos=0
|
|
4
|
+
|
|
5
|
+
# Find position of "--"
|
|
6
|
+
for (( i=1; i <= $#words; i++ )); do
|
|
7
|
+
if [[ "${words[$i]}" == "--" ]]; then
|
|
8
|
+
double_dash_pos=$i
|
|
9
|
+
break
|
|
10
|
+
fi
|
|
11
|
+
done
|
|
12
|
+
|
|
13
|
+
if (( double_dash_pos > 0 && CURRENT > double_dash_pos )); then
|
|
14
|
+
# Delegate to the wrapped command's completion
|
|
15
|
+
words=("${words[@]:$double_dash_pos}")
|
|
16
|
+
CURRENT=$(( CURRENT - double_dash_pos ))
|
|
17
|
+
_normal # let zsh look up the right completion for inner_cmd
|
|
18
|
+
else
|
|
19
|
+
# Complete bing-wrapper's own options
|
|
20
|
+
_arguments \
|
|
21
|
+
'--end[play sound at end]' \
|
|
22
|
+
'*--pattern[pattern to listen for]:pattern:' \
|
|
23
|
+
'--sound-end[sound file at end]:file:_files' \
|
|
24
|
+
'--sound-pattern[sound file for pattern]:file:_files' \
|
|
25
|
+
'--sound-while[sound file for duration of command]:file:_files' \
|
|
26
|
+
'--[inner command]:command:_normal' \
|
|
27
|
+
fi
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
compdef _bing_wrapper bing-wrapper
|
|
@@ -21,21 +21,55 @@ from nate2_scripts.console.hashGrabber import HashGrabber
|
|
|
21
21
|
from nate2_scripts.console.regionCache import RegionCache
|
|
22
22
|
from nate2_scripts.console.sessionCache import SessionCache
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
def get_account_id() -> str:
|
|
26
|
-
return boto3.client("sts").get_caller_identity()["Account"]
|
|
24
|
+
sts = boto3.client("sts")
|
|
27
25
|
|
|
28
26
|
|
|
29
|
-
def
|
|
27
|
+
def get_email() -> str:
|
|
30
28
|
result = subprocess.run(
|
|
31
|
-
["
|
|
32
|
-
capture_output=True,
|
|
33
|
-
text=True,
|
|
29
|
+
["git", "config", "user.email"], capture_output=True, text=True
|
|
34
30
|
)
|
|
35
|
-
|
|
36
|
-
|
|
31
|
+
return result.stdout.strip()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def get_account_id() -> str:
|
|
35
|
+
return sts.get_caller_identity()["Account"]
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def get_credentials(account_id: str) -> dict:
|
|
39
|
+
# Check if already in an assumed role with the correct session name
|
|
40
|
+
identity = sts.get_caller_identity()
|
|
41
|
+
arn = identity.get("Arn", "")
|
|
42
|
+
email = get_email()
|
|
43
|
+
if f"assumed-role/" in arn and arn.endswith(f"/{email}"):
|
|
44
|
+
result = subprocess.run(
|
|
45
|
+
["aws", "configure", "export-credentials"],
|
|
46
|
+
capture_output=True,
|
|
47
|
+
text=True,
|
|
48
|
+
)
|
|
49
|
+
if result.returncode == 0:
|
|
50
|
+
return json.loads(result.stdout)
|
|
51
|
+
|
|
52
|
+
profile = os.environ.get("AWS_PROFILE", "default")
|
|
53
|
+
session = boto3.Session(profile_name=profile)
|
|
54
|
+
config = session._session.get_scoped_config()
|
|
55
|
+
|
|
56
|
+
role_arn = config.get("role_arn", f"arn:aws:iam::{account_id}:role/OrganizationAccountAccessRole")
|
|
57
|
+
source_profile = config.get("source_profile")
|
|
58
|
+
|
|
59
|
+
if source_profile:
|
|
60
|
+
source_sts = boto3.Session(profile_name=source_profile).client("sts")
|
|
61
|
+
else:
|
|
62
|
+
source_sts = sts
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
response = source_sts.assume_role(
|
|
66
|
+
RoleArn=role_arn,
|
|
67
|
+
RoleSessionName=email,
|
|
68
|
+
)
|
|
69
|
+
except Exception as e:
|
|
70
|
+
print(f"Error: Failed to assume role {role_arn}.\n{e}", file=sys.stderr)
|
|
37
71
|
sys.exit(1)
|
|
38
|
-
return
|
|
72
|
+
return response["Credentials"]
|
|
39
73
|
|
|
40
74
|
|
|
41
75
|
def get_signin_token(creds: dict) -> str:
|
|
@@ -115,15 +149,15 @@ def parse_args(region_cache: RegionCache) -> argparse.Namespace:
|
|
|
115
149
|
|
|
116
150
|
|
|
117
151
|
def main() -> None:
|
|
118
|
-
profile_name = get_profile()
|
|
119
152
|
region_cache = RegionCache()
|
|
153
|
+
args = parse_args(region_cache)
|
|
154
|
+
|
|
155
|
+
profile_name = get_profile()
|
|
120
156
|
session_cache = SessionCache()
|
|
121
157
|
|
|
122
158
|
print("Loading regions from cache:", region_cache.get_regions())
|
|
123
159
|
print("Loading default region from cache:", region_cache.get_default_region())
|
|
124
160
|
|
|
125
|
-
args = parse_args(region_cache)
|
|
126
|
-
|
|
127
161
|
if args.force:
|
|
128
162
|
print("Force passed. Reseting session cache.")
|
|
129
163
|
session_cache.reset()
|
|
@@ -131,8 +165,8 @@ def main() -> None:
|
|
|
131
165
|
if args.region:
|
|
132
166
|
print("Default region is now:", args.region)
|
|
133
167
|
|
|
134
|
-
creds = get_credentials()
|
|
135
168
|
account_id = get_account_id()
|
|
169
|
+
creds = get_credentials(account_id)
|
|
136
170
|
hash = session_cache.get_session_hash(profile_name, creds)
|
|
137
171
|
|
|
138
172
|
multisession_domain_name = ""
|
|
@@ -172,3 +206,6 @@ def main() -> None:
|
|
|
172
206
|
|
|
173
207
|
if __name__ == "__main__":
|
|
174
208
|
main()
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
# set user to OrganizationAccountAccessRole/nwestfall@tennex.io
|
{nate2_scripts-1.0.1 → nate2_scripts-1.0.3}/src/nate2_scripts/console/destitationResolver.py
RENAMED
|
@@ -107,6 +107,9 @@ class DestinationResolver:
|
|
|
107
107
|
"ct": f"https://{mdn}{region}.console.aws.amazon.com/cloudtrailv2/",
|
|
108
108
|
"fsx": f"https://{mdn}{region}.console.aws.amazon.com/fsx/",
|
|
109
109
|
"subnet": f"https://{mdn}{region}.console.aws.amazon.com/vpcconsole/home#subnets:",
|
|
110
|
+
"ssm": f"https://{mdn}{region}.console.aws.amazon.com/systems-manager/",
|
|
111
|
+
"parameter": f"https://{mdn}{region}.console.aws.amazon.com/systems-manager/parameters/",
|
|
112
|
+
"ps": f"https://{mdn}{region}.console.aws.amazon.com/systems-manager/parameters/",
|
|
110
113
|
# unchecked
|
|
111
114
|
}
|
|
112
115
|
|
|
@@ -21,13 +21,14 @@ class NameResolver:
|
|
|
21
21
|
def resolve_name(self, name: str) -> str | None:
|
|
22
22
|
regions = self.region_cache.get_regions()
|
|
23
23
|
|
|
24
|
-
# Priority: 0=logs, 1=ec2, 2=s3, 3=cloudformation
|
|
24
|
+
# Priority: 0=logs, 1=ec2, 2=s3, 3=cloudformation, 4=lambda
|
|
25
25
|
tasks: list[tuple] = []
|
|
26
26
|
for region in regions:
|
|
27
27
|
tasks.append((0, self._resolve_log_group, name, region))
|
|
28
|
-
tasks.append((
|
|
28
|
+
tasks.append((2, self._resolve_ec2_instance, name, region))
|
|
29
29
|
tasks.append((3, self._resolve_cloudformation_stack, name, region))
|
|
30
|
-
|
|
30
|
+
tasks.append((4, self._resolve_lambda_function, name, region))
|
|
31
|
+
tasks.append((1, self._resolve_s3_bucket, name))
|
|
31
32
|
|
|
32
33
|
results: dict[int, str] = {}
|
|
33
34
|
|
|
@@ -94,6 +95,12 @@ class NameResolver:
|
|
|
94
95
|
except Exception:
|
|
95
96
|
pass
|
|
96
97
|
return None
|
|
97
|
-
|
|
98
|
-
|
|
99
98
|
|
|
99
|
+
def _resolve_lambda_function(self, name: str, region: str) -> str | None:
|
|
100
|
+
try:
|
|
101
|
+
boto3.client("lambda", region_name=region).get_function(FunctionName=name)
|
|
102
|
+
base = f"https://{self.mdn}{region}.console.aws.amazon.com"
|
|
103
|
+
return f"{base}/lambda/home?region={region}#/functions/{name}"
|
|
104
|
+
except ClientError:
|
|
105
|
+
pass
|
|
106
|
+
return None
|
|
@@ -77,14 +77,16 @@ class ResourceIdResolver:
|
|
|
77
77
|
# keeping it here in case I figure out later
|
|
78
78
|
# if re.match(r"^fl-[0-9a-f]{8,17}$", resource_id):
|
|
79
79
|
# return "fl"
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
if resource_id.
|
|
83
|
-
|
|
84
|
-
if resource_id.startswith("
|
|
85
|
-
|
|
86
|
-
if resource_id.startswith("
|
|
87
|
-
|
|
80
|
+
|
|
81
|
+
# not tested
|
|
82
|
+
# if self._KMS_UUID.match(resource_id) or self._KMS_MRK.match(resource_id):
|
|
83
|
+
# return "kms"
|
|
84
|
+
# if resource_id.startswith("db/"):
|
|
85
|
+
# return "rds"
|
|
86
|
+
# if resource_id.startswith("role/"):
|
|
87
|
+
# return "role"
|
|
88
|
+
# if resource_id.startswith("usr/"):
|
|
89
|
+
# return "user"
|
|
88
90
|
return None
|
|
89
91
|
|
|
90
92
|
def _resolve_in_region(
|
|
@@ -59,9 +59,13 @@ class SessionCache:
|
|
|
59
59
|
|
|
60
60
|
# Honour the STS credential expiration if present
|
|
61
61
|
expiration = creds.get("Expiration")
|
|
62
|
+
print(expiration)
|
|
62
63
|
if expiration:
|
|
63
64
|
try:
|
|
64
|
-
|
|
65
|
+
if isinstance(expiration, datetime):
|
|
66
|
+
exp_time = expiration
|
|
67
|
+
else:
|
|
68
|
+
exp_time = datetime.fromisoformat(expiration.replace("Z", "+00:00"))
|
|
65
69
|
if datetime.now(timezone.utc) >= exp_time:
|
|
66
70
|
return None
|
|
67
71
|
except ValueError:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|