nate2-scripts 1.0.0__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.
Files changed (21) hide show
  1. nate2_scripts-1.0.3/.github/workflows/publish.yml +64 -0
  2. nate2_scripts-1.0.3/.gitignore +3 -0
  3. {nate2_scripts-1.0.0 → nate2_scripts-1.0.3}/PKG-INFO +2 -2
  4. {nate2_scripts-1.0.0 → nate2_scripts-1.0.3}/README.md +18 -11
  5. {nate2_scripts-1.0.0 → nate2_scripts-1.0.3}/pyproject.toml +4 -1
  6. nate2_scripts-1.0.0/src/nate2_scripts/bing_wrapper.py → nate2_scripts-1.0.3/src/nate2_scripts/bing_wrapper/__init__.py +41 -0
  7. nate2_scripts-1.0.3/src/nate2_scripts/bing_wrapper/autocomplete.sh +30 -0
  8. {nate2_scripts-1.0.0 → nate2_scripts-1.0.3}/src/nate2_scripts/console/__init__.py +51 -14
  9. {nate2_scripts-1.0.0 → nate2_scripts-1.0.3}/src/nate2_scripts/console/destitationResolver.py +17 -5
  10. nate2_scripts-1.0.3/src/nate2_scripts/console/nameResolver.py +106 -0
  11. {nate2_scripts-1.0.0 → nate2_scripts-1.0.3}/src/nate2_scripts/console/resourceIdResolver.py +10 -8
  12. {nate2_scripts-1.0.0 → nate2_scripts-1.0.3}/src/nate2_scripts/console/sessionCache.py +5 -1
  13. nate2_scripts-1.0.0/.gitignore +0 -1
  14. nate2_scripts-1.0.0/testing.txt +0 -9
  15. {nate2_scripts-1.0.0 → nate2_scripts-1.0.3}/CONSOLE-NOTES.md +0 -0
  16. {nate2_scripts-1.0.0 → nate2_scripts-1.0.3}/LICENSE +0 -0
  17. {nate2_scripts-1.0.0 → nate2_scripts-1.0.3}/src/nate2_scripts/__init__.py +0 -0
  18. {nate2_scripts-1.0.0 → nate2_scripts-1.0.3}/src/nate2_scripts/console/arnResolver.py +0 -0
  19. {nate2_scripts-1.0.0 → nate2_scripts-1.0.3}/src/nate2_scripts/console/hashGrabber.py +0 -0
  20. {nate2_scripts-1.0.0 → nate2_scripts-1.0.3}/src/nate2_scripts/console/regionCache.py +0 -0
  21. {nate2_scripts-1.0.0 → nate2_scripts-1.0.3}/src/nate2_scripts/test.py +0 -0
@@ -0,0 +1,64 @@
1
+ name: Publish to PyPI
2
+
3
+ # Trigger this workflow on every push to the main branch.
4
+ # To prevent unauthorized publishes, protect main with branch protection rules
5
+ # (require PRs + approvals) and add required reviewers to the "pypi" environment.
6
+ on:
7
+ push:
8
+ branches:
9
+ - main
10
+
11
+ jobs:
12
+ build:
13
+ name: Build distribution
14
+ runs-on: ubuntu-latest
15
+
16
+ steps:
17
+ # GitHub Actions run on fresh virtual machines that have no knowledge of your code.
18
+ # This step clones your repository into the runner so subsequent steps can access your files.
19
+ - uses: actions/checkout@v4
20
+
21
+ - name: Set up Python
22
+ uses: actions/setup-python@v5
23
+ with:
24
+ python-version: "3.x"
25
+
26
+ - name: Install Hatch
27
+ run: pip install hatch
28
+
29
+ # Produces a source distribution (sdist) and a wheel in the dist/ directory.
30
+ # The version published is whatever is set in pyproject.toml — bump it before
31
+ # merging to main, otherwise PyPI will reject a duplicate version.
32
+ - name: Build package
33
+ run: hatch build
34
+
35
+ # Pass the built artifacts to the publish job via GitHub's artifact storage.
36
+ - name: Upload distribution artifacts
37
+ uses: actions/upload-artifact@v4
38
+ with:
39
+ name: dist
40
+ path: dist/
41
+
42
+ publish:
43
+ name: Publish to PyPI
44
+ needs: build # only runs after the build job succeeds
45
+ runs-on: ubuntu-latest
46
+ # The "pypi" environment can be configured in GitHub (Settings → Environments)
47
+ # to require manual approval before this job runs, adding an extra gate on publishing.
48
+ environment:
49
+ name: pypi
50
+ url: https://pypi.org/p/nate2-scripts
51
+ permissions:
52
+ id-token: write # required for Trusted Publishing (OIDC) — no API token needed
53
+
54
+ steps:
55
+ - name: Download distribution artifacts
56
+ uses: actions/download-artifact@v4
57
+ with:
58
+ name: dist
59
+ path: dist/
60
+
61
+ # Uses PyPA's official action with Trusted Publishing (OIDC).
62
+ # PyPI must be configured with a Trusted Publisher pointing at this repo/workflow.
63
+ - name: Publish to PyPI
64
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,3 @@
1
+ __pycache__
2
+ dist
3
+ .vscode
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: nate2-scripts
3
- Version: 1.0.0
3
+ Version: 1.0.3
4
4
  Summary: Utility scripts for AWS development on macOS
5
5
  Project-URL: Homepage, https://github.com/tennex-nwestfall/nate2-scripts
6
6
  License-File: LICENSE
@@ -1,3 +1,6 @@
1
+ [![PyPI version](https://img.shields.io/pypi/v/nate2-scripts)](https://pypi.org/project/nate2-scripts/)
2
+ [![License](https://img.shields.io/github/license/tennex-nwestfall/nate2-scripts)](https://github.com/tennex-nwestfall/nate2-scripts/blob/main/LICENSE)
3
+
1
4
  # Nate2's Scripts
2
5
 
3
6
  Utility scripts for AWS development on macOS.
@@ -5,7 +8,7 @@ Utility scripts for AWS development on macOS.
5
8
  To install run:
6
9
 
7
10
  ```bash
8
- pip install git+https://github.com/tennex-nwestfall/scripts.git
11
+ pip install nate2-scripts
9
12
  ```
10
13
 
11
14
  ## Scripts
@@ -31,7 +34,7 @@ eval "$(register-python-argcomplete console)"
31
34
  **Usage:**
32
35
 
33
36
  ```bash
34
- # change to an account/role
37
+ # change to an account/role
35
38
  asp my-aws-role
36
39
 
37
40
  # open main page of the role/account
@@ -42,8 +45,8 @@ console ec2
42
45
  console s3
43
46
  # open lambda page of the role/account in us-west-2
44
47
  console lambda -r us-west-2
45
- # open instance id page of the role/account in
46
- # whatever region that instance is in
48
+ # open instance id page of the role/account in
49
+ # whatever region that instance is in
47
50
  console i-0123456789abcdef
48
51
  # open the console page for the resource the ARN points to
49
52
  console arn:aws:ec2:us-east-1:012345678967:instance/i-0123456789abcdef
@@ -56,7 +59,7 @@ console arn:aws:ec2:us-east-1:012345678967:instance/i-0123456789abcdef
56
59
  **Supported ARNs:** `ec2`, `iam` (role, user), `lambda`, `logs` (CloudWatch Logs), `rds`, `s3`, `secretsmanager`, `ecs`, `eks`, `sns`, `sqs`, `batch`, `dynamodb`, `states` (Step Functions), `elasticloadbalancing`
57
60
 
58
61
  **All Regions Supported:**
59
- Edit `console-regions.json` to change which regions are evaluated and to set the default one
62
+ Edit `~/.local/state/console-regions.json` to change which regions are evaluated and to set the default one
60
63
 
61
64
  ---
62
65
 
@@ -74,13 +77,17 @@ alias cdk="bing-wrapper.py --end --pattern \"(y/n)\" --sound-end /System/Library
74
77
 
75
78
  # wraps nextflow to play a sound when done
76
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)"
77
83
  ```
78
84
 
79
85
  **Options:**
80
86
 
81
- | Flag | Description | Default |
82
- | --- | --- | --- |
83
- | `--end` | Play a sound when the command exits | off |
84
- | `--pattern <text>` | Play a sound when `<text>` appears in output (repeatable) | none |
85
- | `--sound-end <path>` | Sound file to play on exit | `Glass.aiff` |
86
- | `--sound-pattern <path>` | Sound file to play on pattern match | `Glass.aiff` |
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.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 get_credentials() -> dict:
27
+ def get_email() -> str:
30
28
  result = subprocess.run(
31
- ["aws", "configure", "export-credentials"],
32
- capture_output=True,
33
- text=True,
29
+ ["git", "config", "user.email"], capture_output=True, text=True
34
30
  )
35
- if result.returncode != 0:
36
- print(f"Error: Failed to export credentials.\n{result.stderr}", file=sys.stderr)
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 json.loads(result.stdout)
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
@@ -4,7 +4,8 @@ import sys
4
4
  from dataclasses import dataclass
5
5
  import boto3
6
6
  from nate2_scripts.console.arnResolver import ArnResolver
7
- from nate2_scripts.console.regionCache import DEFAULT_REGION, RegionCache
7
+ from nate2_scripts.console.nameResolver import NameResolver
8
+ from nate2_scripts.console.regionCache import RegionCache
8
9
  from botocore.exceptions import ClientError
9
10
  import urllib.parse
10
11
 
@@ -36,6 +37,9 @@ class DestinationResolver:
36
37
  if not service:
37
38
  return f"https://{self.mdn}{default_region}.console.aws.amazon.com/"
38
39
 
40
+ if service.lower() in service_urls:
41
+ return service_urls[service.lower()]
42
+
39
43
  arn_dest = ArnResolver(self.account,self.mdn).try_parse_arn(service)
40
44
  if arn_dest:
41
45
  return arn_dest
@@ -44,10 +48,13 @@ class DestinationResolver:
44
48
  if id_dest:
45
49
  return id_dest
46
50
 
47
- if service.lower() not in service_urls:
48
- print(f"Error: Unknown service '{service}'.", file=sys.stderr)
49
- sys.exit(1)
50
- return service_urls[service.lower()]
51
+ name_dest = NameResolver(self.region_cache, self.mdn).resolve_name(service)
52
+ if name_dest:
53
+ return name_dest
54
+
55
+ print(f"Error: Unknown service '{service}'.", file=sys.stderr)
56
+ sys.exit(1)
57
+
51
58
 
52
59
  @staticmethod
53
60
  def _get_services(region: str, mdn: str = "") -> dict[str, str]:
@@ -98,6 +105,11 @@ class DestinationResolver:
98
105
  "athena": f"https://{mdn}{region}.console.aws.amazon.com/athena/home#/query-editor",
99
106
  "cloudtrail": f"https://{mdn}{region}.console.aws.amazon.com/cloudtrailv2/",
100
107
  "ct": f"https://{mdn}{region}.console.aws.amazon.com/cloudtrailv2/",
108
+ "fsx": f"https://{mdn}{region}.console.aws.amazon.com/fsx/",
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/",
101
113
  # unchecked
102
114
  }
103
115
 
@@ -0,0 +1,106 @@
1
+ import re
2
+ import urllib.parse
3
+ from concurrent.futures import ThreadPoolExecutor, as_completed
4
+
5
+ import boto3
6
+ from botocore.exceptions import ClientError
7
+
8
+ from nate2_scripts.console.regionCache import RegionCache
9
+
10
+ MAX_THREADS = 10
11
+
12
+
13
+ class NameResolver:
14
+ region_cache: RegionCache
15
+ mdn: str
16
+
17
+ def __init__(self, region_cache: RegionCache, multisession_domain_name: str):
18
+ self.region_cache = region_cache
19
+ self.mdn = multisession_domain_name
20
+
21
+ def resolve_name(self, name: str) -> str | None:
22
+ regions = self.region_cache.get_regions()
23
+
24
+ # Priority: 0=logs, 1=ec2, 2=s3, 3=cloudformation, 4=lambda
25
+ tasks: list[tuple] = []
26
+ for region in regions:
27
+ tasks.append((0, self._resolve_log_group, name, region))
28
+ tasks.append((2, self._resolve_ec2_instance, name, region))
29
+ tasks.append((3, self._resolve_cloudformation_stack, name, region))
30
+ tasks.append((4, self._resolve_lambda_function, name, region))
31
+ tasks.append((1, self._resolve_s3_bucket, name))
32
+
33
+ results: dict[int, str] = {}
34
+
35
+ with ThreadPoolExecutor(max_workers=MAX_THREADS) as executor:
36
+ future_to_priority = {
37
+ executor.submit(fn, *args): priority
38
+ for priority, fn, *args in tasks
39
+ }
40
+ for future in as_completed(future_to_priority):
41
+ priority = future_to_priority[future]
42
+ url = future.result()
43
+ if url is not None and priority not in results:
44
+ results[priority] = url
45
+
46
+ for priority in sorted(results):
47
+ return results[priority]
48
+ return None
49
+
50
+ def _resolve_log_group(self, name: str, region: str) -> str | None:
51
+ try:
52
+ result = boto3.client("logs", region_name=region).describe_log_groups(
53
+ logGroupNamePrefix=name
54
+ )
55
+ if any(lg["logGroupName"] == name for lg in result.get("logGroups", [])):
56
+ base = f"https://{self.mdn}{region}.console.aws.amazon.com"
57
+ encoded = urllib.parse.quote(name, safe="")
58
+ return f"{base}/cloudwatch/home?region={region}#logsV2:log-groups/log-group/{encoded}"
59
+ except ClientError:
60
+ pass
61
+ return None
62
+
63
+ def _resolve_ec2_instance(self, name: str, region: str) -> str | None:
64
+ try:
65
+ result = boto3.client("ec2", region_name=region).describe_instances(
66
+ Filters=[{"Name": "tag:Name", "Values": [name]}]
67
+ )
68
+ reservations = result.get("Reservations", [])
69
+ if reservations:
70
+ base = f"https://{self.mdn}{region}.console.aws.amazon.com"
71
+ instance_id = reservations[0]["Instances"][0]["InstanceId"]
72
+ return f"{base}/ec2/v2/home?region={region}#InstanceDetails:instanceId={instance_id}"
73
+ except ClientError:
74
+ pass
75
+ return None
76
+
77
+ def _resolve_cloudformation_stack(self, name: str, region: str) -> str | None:
78
+ try:
79
+ result = boto3.client("cloudformation", region_name=region).describe_stacks(
80
+ StackName=name
81
+ )
82
+ stacks = result.get("Stacks", [])
83
+ if stacks:
84
+ base = f"https://{self.mdn}{region}.console.aws.amazon.com"
85
+ stack_id = urllib.parse.quote(stacks[0]["StackId"], safe="")
86
+ return f"{base}/cloudformation/home?region={region}#/stacks/stackinfo?stackId={stack_id}"
87
+ except ClientError:
88
+ pass
89
+ return None
90
+
91
+ def _resolve_s3_bucket(self, name: str) -> str | None:
92
+ try:
93
+ boto3.client("s3").head_bucket(Bucket=name)
94
+ return f"https://{self.mdn}us-east-1.console.aws.amazon.com/s3/buckets/{name}"
95
+ except Exception:
96
+ pass
97
+ return None
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
- if self._KMS_UUID.match(resource_id) or self._KMS_MRK.match(resource_id):
81
- return "kms"
82
- if resource_id.startswith("db/"):
83
- return "rds"
84
- if resource_id.startswith("role/"):
85
- return "role"
86
- if resource_id.startswith("usr/"):
87
- return "user"
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
- exp_time = datetime.fromisoformat(expiration.replace("Z", "+00:00"))
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:
@@ -1 +0,0 @@
1
- __pycache__
@@ -1,9 +0,0 @@
1
- ec2
2
-
3
- i-04338f521d58918dd
4
-
5
- arn:aws:ec2:us-east-1:905502466771:instance/i-04338f521d58918dd
6
-
7
- arn:aws:logs:us-east-1:905502466771:log-group:/aws/parallelcluster/pcluster-lab-nate2-202607062002:*
8
-
9
- arn:aws:logs:us-west-2:905502466771:log-group:/aws/lambda/InternalTestingStack-1-TagBucketLambda1DCC2FF9-dwonHDanL8Xf:*
File without changes