nate2-scripts 1.0.0__tar.gz → 1.0.1__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.
@@ -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
1
  Metadata-Version: 2.4
2
2
  Name: nate2-scripts
3
- Version: 1.0.0
3
+ Version: 1.0.1
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,7 @@
1
+
2
+ [![PyPI version](https://img.shields.io/pypi/v/nate2-scripts)](https://pypi.org/project/nate2-scripts/)
3
+ [![License](https://img.shields.io/github/license/tennex-nwestfall/nate2-scripts)](https://github.com/tennex-nwestfall/nate2-scripts/blob/main/LICENSE)
4
+
1
5
  # Nate2's Scripts
2
6
 
3
7
  Utility scripts for AWS development on macOS.
@@ -5,7 +9,7 @@ Utility scripts for AWS development on macOS.
5
9
  To install run:
6
10
 
7
11
  ```bash
8
- pip install git+https://github.com/tennex-nwestfall/scripts.git
12
+ pip install nate2-scripts
9
13
  ```
10
14
 
11
15
  ## Scripts
@@ -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.1"
8
8
  description = "Utility scripts for AWS development on macOS"
9
9
  requires-python = ">=3.11"
10
10
  dependencies = [
@@ -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,8 @@ 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:",
101
110
  # unchecked
102
111
  }
103
112
 
@@ -0,0 +1,99 @@
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
25
+ tasks: list[tuple] = []
26
+ for region in regions:
27
+ tasks.append((0, self._resolve_log_group, name, region))
28
+ tasks.append((1, self._resolve_ec2_instance, name, region))
29
+ tasks.append((3, self._resolve_cloudformation_stack, name, region))
30
+ tasks.append((2, self._resolve_s3_bucket, name))
31
+
32
+ results: dict[int, str] = {}
33
+
34
+ with ThreadPoolExecutor(max_workers=MAX_THREADS) as executor:
35
+ future_to_priority = {
36
+ executor.submit(fn, *args): priority
37
+ for priority, fn, *args in tasks
38
+ }
39
+ for future in as_completed(future_to_priority):
40
+ priority = future_to_priority[future]
41
+ url = future.result()
42
+ if url is not None and priority not in results:
43
+ results[priority] = url
44
+
45
+ for priority in sorted(results):
46
+ return results[priority]
47
+ return None
48
+
49
+ def _resolve_log_group(self, name: str, region: str) -> str | None:
50
+ try:
51
+ result = boto3.client("logs", region_name=region).describe_log_groups(
52
+ logGroupNamePrefix=name
53
+ )
54
+ if any(lg["logGroupName"] == name for lg in result.get("logGroups", [])):
55
+ base = f"https://{self.mdn}{region}.console.aws.amazon.com"
56
+ encoded = urllib.parse.quote(name, safe="")
57
+ return f"{base}/cloudwatch/home?region={region}#logsV2:log-groups/log-group/{encoded}"
58
+ except ClientError:
59
+ pass
60
+ return None
61
+
62
+ def _resolve_ec2_instance(self, name: str, region: str) -> str | None:
63
+ try:
64
+ result = boto3.client("ec2", region_name=region).describe_instances(
65
+ Filters=[{"Name": "tag:Name", "Values": [name]}]
66
+ )
67
+ reservations = result.get("Reservations", [])
68
+ if reservations:
69
+ base = f"https://{self.mdn}{region}.console.aws.amazon.com"
70
+ instance_id = reservations[0]["Instances"][0]["InstanceId"]
71
+ return f"{base}/ec2/v2/home?region={region}#InstanceDetails:instanceId={instance_id}"
72
+ except ClientError:
73
+ pass
74
+ return None
75
+
76
+ def _resolve_cloudformation_stack(self, name: str, region: str) -> str | None:
77
+ try:
78
+ result = boto3.client("cloudformation", region_name=region).describe_stacks(
79
+ StackName=name
80
+ )
81
+ stacks = result.get("Stacks", [])
82
+ if stacks:
83
+ base = f"https://{self.mdn}{region}.console.aws.amazon.com"
84
+ stack_id = urllib.parse.quote(stacks[0]["StackId"], safe="")
85
+ return f"{base}/cloudformation/home?region={region}#/stacks/stackinfo?stackId={stack_id}"
86
+ except ClientError:
87
+ pass
88
+ return None
89
+
90
+ def _resolve_s3_bucket(self, name: str) -> str | None:
91
+ try:
92
+ boto3.client("s3").head_bucket(Bucket=name)
93
+ return f"https://{self.mdn}us-east-1.console.aws.amazon.com/s3/buckets/{name}"
94
+ except Exception:
95
+ pass
96
+ return None
97
+
98
+
99
+
@@ -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