nate2-scripts 1.0.3__tar.gz → 2.0.0__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.3 → nate2_scripts-2.0.0}/.gitignore +2 -1
- {nate2_scripts-1.0.3 → nate2_scripts-2.0.0}/PKG-INFO +5 -1
- nate2_scripts-2.0.0/README.md +204 -0
- nate2_scripts-2.0.0/i-want-to-refactor-fizzy-feather.md +173 -0
- nate2_scripts-2.0.0/plan.md +16 -0
- {nate2_scripts-1.0.3 → nate2_scripts-2.0.0}/pyproject.toml +14 -1
- {nate2_scripts-1.0.3 → nate2_scripts-2.0.0}/src/nate2_scripts/bing_wrapper/__init__.py +4 -2
- nate2_scripts-2.0.0/src/nate2_scripts/console/__init__.py +207 -0
- nate2_scripts-2.0.0/src/nate2_scripts/console/boto3_session.py +51 -0
- nate2_scripts-1.0.3/src/nate2_scripts/console/hashGrabber.py → nate2_scripts-2.0.0/src/nate2_scripts/console/hash_grabber.py +27 -12
- nate2_scripts-2.0.0/src/nate2_scripts/console/local_refresh.py +0 -0
- nate2_scripts-2.0.0/src/nate2_scripts/console/resolvers/__init__.py +44 -0
- nate2_scripts-2.0.0/src/nate2_scripts/console/resolvers/apigateway.py +11 -0
- nate2_scripts-2.0.0/src/nate2_scripts/console/resolvers/artifact.py +11 -0
- nate2_scripts-2.0.0/src/nate2_scripts/console/resolvers/athena.py +11 -0
- nate2_scripts-2.0.0/src/nate2_scripts/console/resolvers/batch.py +52 -0
- nate2_scripts-2.0.0/src/nate2_scripts/console/resolvers/bedrock.py +11 -0
- nate2_scripts-2.0.0/src/nate2_scripts/console/resolvers/cloudformation.py +28 -0
- nate2_scripts-2.0.0/src/nate2_scripts/console/resolvers/cloudfront.py +12 -0
- nate2_scripts-2.0.0/src/nate2_scripts/console/resolvers/cloudhsm.py +11 -0
- nate2_scripts-2.0.0/src/nate2_scripts/console/resolvers/cloudtrail.py +11 -0
- nate2_scripts-2.0.0/src/nate2_scripts/console/resolvers/cognito.py +11 -0
- nate2_scripts-2.0.0/src/nate2_scripts/console/resolvers/config.py +11 -0
- nate2_scripts-2.0.0/src/nate2_scripts/console/resolvers/controltower.py +11 -0
- nate2_scripts-2.0.0/src/nate2_scripts/console/resolvers/cost.py +13 -0
- nate2_scripts-2.0.0/src/nate2_scripts/console/resolvers/dynamo.py +27 -0
- nate2_scripts-2.0.0/src/nate2_scripts/console/resolvers/ec2.py +118 -0
- nate2_scripts-2.0.0/src/nate2_scripts/console/resolvers/ecr.py +16 -0
- nate2_scripts-2.0.0/src/nate2_scripts/console/resolvers/ecs.py +61 -0
- nate2_scripts-2.0.0/src/nate2_scripts/console/resolvers/efs.py +11 -0
- nate2_scripts-2.0.0/src/nate2_scripts/console/resolvers/eks.py +11 -0
- nate2_scripts-2.0.0/src/nate2_scripts/console/resolvers/elasticbeanstalk.py +11 -0
- nate2_scripts-2.0.0/src/nate2_scripts/console/resolvers/elb.py +35 -0
- nate2_scripts-2.0.0/src/nate2_scripts/console/resolvers/eventbridge.py +11 -0
- nate2_scripts-2.0.0/src/nate2_scripts/console/resolvers/fsx.py +11 -0
- nate2_scripts-2.0.0/src/nate2_scripts/console/resolvers/glue.py +11 -0
- nate2_scripts-2.0.0/src/nate2_scripts/console/resolvers/guardduty.py +11 -0
- nate2_scripts-2.0.0/src/nate2_scripts/console/resolvers/iam.py +43 -0
- nate2_scripts-2.0.0/src/nate2_scripts/console/resolvers/inspector.py +11 -0
- nate2_scripts-2.0.0/src/nate2_scripts/console/resolvers/kms.py +11 -0
- nate2_scripts-2.0.0/src/nate2_scripts/console/resolvers/lambda_.py +45 -0
- nate2_scripts-2.0.0/src/nate2_scripts/console/resolvers/lex.py +11 -0
- nate2_scripts-2.0.0/src/nate2_scripts/console/resolvers/lightsail.py +11 -0
- nate2_scripts-2.0.0/src/nate2_scripts/console/resolvers/logs.py +81 -0
- nate2_scripts-2.0.0/src/nate2_scripts/console/resolvers/marketplace.py +24 -0
- nate2_scripts-2.0.0/src/nate2_scripts/console/resolvers/polly.py +11 -0
- nate2_scripts-2.0.0/src/nate2_scripts/console/resolvers/quick.py +15 -0
- nate2_scripts-2.0.0/src/nate2_scripts/console/resolvers/rds.py +34 -0
- nate2_scripts-2.0.0/src/nate2_scripts/console/resolvers/route53.py +16 -0
- nate2_scripts-2.0.0/src/nate2_scripts/console/resolvers/s3.py +66 -0
- nate2_scripts-2.0.0/src/nate2_scripts/console/resolvers/sagemaker.py +11 -0
- nate2_scripts-2.0.0/src/nate2_scripts/console/resolvers/secretsmanager.py +31 -0
- nate2_scripts-2.0.0/src/nate2_scripts/console/resolvers/sns.py +29 -0
- nate2_scripts-2.0.0/src/nate2_scripts/console/resolvers/sqs.py +28 -0
- nate2_scripts-2.0.0/src/nate2_scripts/console/resolvers/ssm.py +18 -0
- nate2_scripts-2.0.0/src/nate2_scripts/console/resolvers/stepfunctions.py +37 -0
- nate2_scripts-2.0.0/src/nate2_scripts/console/resolvers/storagegateway.py +11 -0
- nate2_scripts-2.0.0/src/nate2_scripts/console/resolvers/support.py +14 -0
- nate2_scripts-2.0.0/src/nate2_scripts/console/resolvers/vpc.py +93 -0
- nate2_scripts-1.0.3/src/nate2_scripts/console/sessionCache.py → nate2_scripts-2.0.0/src/nate2_scripts/console/session_cache.py +8 -26
- nate2_scripts-2.0.0/src/nate2_scripts/console/text_resolver.py +193 -0
- nate2_scripts-2.0.0/src/nate2_scripts/console/types.py +59 -0
- {nate2_scripts-1.0.3/src/nate2_scripts → nate2_scripts-2.0.0}/test.py +4 -2
- nate2_scripts-2.0.0/tests/__init__.py +0 -0
- nate2_scripts-2.0.0/tests/console/__init__.py +0 -0
- nate2_scripts-2.0.0/tests/console/resolvers/__init__.py +29 -0
- nate2_scripts-2.0.0/tests/console/resolvers/apigateway.py +34 -0
- nate2_scripts-2.0.0/tests/console/resolvers/artifact.py +21 -0
- nate2_scripts-2.0.0/tests/console/resolvers/athena.py +34 -0
- nate2_scripts-2.0.0/tests/console/resolvers/batch.py +50 -0
- nate2_scripts-2.0.0/tests/console/resolvers/bedrock.py +36 -0
- nate2_scripts-2.0.0/tests/console/resolvers/cloudformation.py +132 -0
- nate2_scripts-2.0.0/tests/console/resolvers/cloudfront.py +34 -0
- nate2_scripts-2.0.0/tests/console/resolvers/cloudhsm.py +34 -0
- nate2_scripts-2.0.0/tests/console/resolvers/cloudtrail.py +34 -0
- nate2_scripts-2.0.0/tests/console/resolvers/cognito.py +34 -0
- nate2_scripts-2.0.0/tests/console/resolvers/config.py +34 -0
- nate2_scripts-2.0.0/tests/console/resolvers/conftest.py +40 -0
- nate2_scripts-2.0.0/tests/console/resolvers/controltower.py +21 -0
- nate2_scripts-2.0.0/tests/console/resolvers/cost.py +22 -0
- nate2_scripts-2.0.0/tests/console/resolvers/dynamo.py +47 -0
- nate2_scripts-2.0.0/tests/console/resolvers/ec2.py +269 -0
- nate2_scripts-2.0.0/tests/console/resolvers/ecr.py +34 -0
- nate2_scripts-2.0.0/tests/console/resolvers/ecs.py +60 -0
- nate2_scripts-2.0.0/tests/console/resolvers/efs.py +21 -0
- nate2_scripts-2.0.0/tests/console/resolvers/eks.py +21 -0
- nate2_scripts-2.0.0/tests/console/resolvers/elasticbeanstalk.py +34 -0
- nate2_scripts-2.0.0/tests/console/resolvers/elb.py +34 -0
- nate2_scripts-2.0.0/tests/console/resolvers/eventbridge.py +34 -0
- nate2_scripts-2.0.0/tests/console/resolvers/fsx.py +21 -0
- nate2_scripts-2.0.0/tests/console/resolvers/glue.py +21 -0
- nate2_scripts-2.0.0/tests/console/resolvers/guardduty.py +34 -0
- nate2_scripts-2.0.0/tests/console/resolvers/iam.py +21 -0
- nate2_scripts-2.0.0/tests/console/resolvers/inspector.py +21 -0
- nate2_scripts-2.0.0/tests/console/resolvers/kms.py +21 -0
- nate2_scripts-2.0.0/tests/console/resolvers/lambda_.py +202 -0
- nate2_scripts-2.0.0/tests/console/resolvers/lex.py +21 -0
- nate2_scripts-2.0.0/tests/console/resolvers/lightsail.py +34 -0
- nate2_scripts-2.0.0/tests/console/resolvers/logs.py +258 -0
- nate2_scripts-2.0.0/tests/console/resolvers/marketplace.py +60 -0
- nate2_scripts-2.0.0/tests/console/resolvers/polly.py +21 -0
- nate2_scripts-2.0.0/tests/console/resolvers/rds.py +21 -0
- nate2_scripts-2.0.0/tests/console/resolvers/route53.py +34 -0
- nate2_scripts-2.0.0/tests/console/resolvers/s3.py +305 -0
- nate2_scripts-2.0.0/tests/console/resolvers/sagemaker.py +34 -0
- nate2_scripts-2.0.0/tests/console/resolvers/secretsmanager.py +47 -0
- nate2_scripts-2.0.0/tests/console/resolvers/sns.py +21 -0
- nate2_scripts-2.0.0/tests/console/resolvers/sqs.py +21 -0
- nate2_scripts-2.0.0/tests/console/resolvers/ssm.py +89 -0
- nate2_scripts-2.0.0/tests/console/resolvers/stepfunctions.py +21 -0
- nate2_scripts-2.0.0/tests/console/resolvers/storagegateway.py +34 -0
- nate2_scripts-2.0.0/tests/console/resolvers/support.py +36 -0
- nate2_scripts-2.0.0/tests/console/resolvers/vpc.py +58 -0
- nate2_scripts-2.0.0/tests/console/test_aws/config +35 -0
- nate2_scripts-2.0.0/tests/console/test_aws/credentials +11 -0
- nate2_scripts-1.0.3/README.md +0 -93
- nate2_scripts-1.0.3/src/nate2_scripts/console/__init__.py +0 -211
- nate2_scripts-1.0.3/src/nate2_scripts/console/arnResolver.py +0 -514
- nate2_scripts-1.0.3/src/nate2_scripts/console/destitationResolver.py +0 -116
- nate2_scripts-1.0.3/src/nate2_scripts/console/nameResolver.py +0 -106
- nate2_scripts-1.0.3/src/nate2_scripts/console/regionCache.py +0 -50
- nate2_scripts-1.0.3/src/nate2_scripts/console/resourceIdResolver.py +0 -176
- {nate2_scripts-1.0.3 → nate2_scripts-2.0.0}/.github/workflows/publish.yml +0 -0
- {nate2_scripts-1.0.3 → nate2_scripts-2.0.0}/CONSOLE-NOTES.md +0 -0
- {nate2_scripts-1.0.3 → nate2_scripts-2.0.0}/LICENSE +0 -0
- {nate2_scripts-1.0.3 → nate2_scripts-2.0.0}/src/nate2_scripts/__init__.py +0 -0
- {nate2_scripts-1.0.3 → nate2_scripts-2.0.0}/src/nate2_scripts/bing_wrapper/autocomplete.sh +0 -0
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: nate2-scripts
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.0.0
|
|
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
|
|
7
7
|
Requires-Python: >=3.11
|
|
8
8
|
Requires-Dist: argcomplete
|
|
9
9
|
Requires-Dist: boto3
|
|
10
|
+
Provides-Extra: dev
|
|
11
|
+
Requires-Dist: boto3-stubs[essential]; extra == 'dev'
|
|
12
|
+
Requires-Dist: moto[all]; extra == 'dev'
|
|
13
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
[](https://pypi.org/project/nate2-scripts/)
|
|
2
|
+
[](https://github.com/tennex-nwestfall/nate2-scripts/blob/main/LICENSE)
|
|
3
|
+
|
|
4
|
+
# Nate2's Scripts
|
|
5
|
+
|
|
6
|
+
Utility scripts for AWS development on macOS.
|
|
7
|
+
|
|
8
|
+
To install run:
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
pip install nate2-scripts
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
To update run:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pip install --upgrade nate2-scripts
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Scripts
|
|
21
|
+
|
|
22
|
+
### `console`
|
|
23
|
+
|
|
24
|
+
Opens the AWS Management Console in your browser for a specific service, ARN, resource id, or resource name, using federated sign-in from your current AWS credentials. Supports multi-session via searching browser history sqlite databases.
|
|
25
|
+
|
|
26
|
+
**Requirements:**
|
|
27
|
+
|
|
28
|
+
- `AWS_PROFILE` set (e.g. via `asp`)
|
|
29
|
+
- `git config user.email` set — it is used as the `role_session_name` for every assume-role profile
|
|
30
|
+
|
|
31
|
+
**Example Alias:**
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
# noglob is so that one can paste in logs arns that end with :*
|
|
35
|
+
# otherwise zsh tries to see the log arn as a glob and fails
|
|
36
|
+
alias co="noglob console"
|
|
37
|
+
# needed for autocomplete
|
|
38
|
+
eval "$(register-python-argcomplete console)"
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**Usage:**
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
console [service | arn | id | name] [search ...] [-r REGION] [-f]
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
# change to an account/role
|
|
49
|
+
asp my-aws-role
|
|
50
|
+
|
|
51
|
+
# open main page of the role/account
|
|
52
|
+
console
|
|
53
|
+
# open ec2 page of the role/account
|
|
54
|
+
console ec2
|
|
55
|
+
# open lambda page of the role/account in us-west-2
|
|
56
|
+
console lambda -r us-west-2
|
|
57
|
+
# open a service page filtered by search terms (multiple words are joined)
|
|
58
|
+
console lambda my-function
|
|
59
|
+
console logs /aws/lambda/my
|
|
60
|
+
console cf my stack
|
|
61
|
+
# open instance id page of the role/account in
|
|
62
|
+
# whatever region that instance is in
|
|
63
|
+
console i-0123456789abcdef
|
|
64
|
+
# open an S3 bucket, prefix, or object
|
|
65
|
+
console s3://my-bucket/some/prefix/
|
|
66
|
+
# open a resource by name (log group, S3 bucket, EC2 Name tag,
|
|
67
|
+
# CloudFormation stack, or Lambda function)
|
|
68
|
+
console my-function
|
|
69
|
+
# open the console page for the resource the ARN points to
|
|
70
|
+
console arn:aws:ec2:us-east-1:012345678967:instance/i-0123456789abcdef
|
|
71
|
+
# force a new federated sign-in (resets the session cache)
|
|
72
|
+
console -f
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
**Options:**
|
|
76
|
+
|
|
77
|
+
| Flag | Description |
|
|
78
|
+
| ----------------------- | ----------------------------------------------------------------- |
|
|
79
|
+
| `-r`, `--region <name>` | Region to open the console in; also added to the searched regions |
|
|
80
|
+
| `-f`, `--force` | Reset the session cache and force a new federated sign-in |
|
|
81
|
+
|
|
82
|
+
**Resolution order:**
|
|
83
|
+
|
|
84
|
+
1. **ARN** — if the first argument parses as an ARN
|
|
85
|
+
2. **Service** — if the first argument is a known service name or alias
|
|
86
|
+
3. **Resource id** — if the first argument matches a known id pattern
|
|
87
|
+
4. **Resource name** — looked up in parallel across all name-resolving services; the first match in priority order wins: CloudWatch log group → S3 bucket → EC2 `Name` tag → CloudFormation stack → Lambda function
|
|
88
|
+
|
|
89
|
+
**Supported services** (aliases after the `/`):
|
|
90
|
+
|
|
91
|
+
| Service | Names | Search terms |
|
|
92
|
+
| ---------------------- | ----------------------------------------- | ------------ |
|
|
93
|
+
| API Gateway | `apigateway` / `api` | |
|
|
94
|
+
| Artifact | `artifact` | |
|
|
95
|
+
| Athena | `athena` | |
|
|
96
|
+
| Batch | `batch` / `job` / `jobs` | |
|
|
97
|
+
| Bedrock | `bedrock` / `br` | |
|
|
98
|
+
| CloudFormation | `cloudformation` / `cf` | ✓ |
|
|
99
|
+
| CloudFront | `cloudfront` / `front` | |
|
|
100
|
+
| CloudHSM | `cloudhsm` / `hsm` | |
|
|
101
|
+
| CloudTrail | `cloudtrail` / `ct` | |
|
|
102
|
+
| CloudWatch | `cloudwatch` / `cw` | |
|
|
103
|
+
| CloudWatch Logs | `logs` | ✓ |
|
|
104
|
+
| Cognito | `cognito` / `cn` | |
|
|
105
|
+
| Config | `config` / `cfg` | |
|
|
106
|
+
| Control Tower | `controltower` | |
|
|
107
|
+
| Cost Management | `cost` | |
|
|
108
|
+
| DynamoDB | `dynamodb` / `dynamo` / `dyn` | |
|
|
109
|
+
| EC2 instances | `ec2` | ✓ |
|
|
110
|
+
| EC2 AMIs (owned by me) | `ami` | ✓ |
|
|
111
|
+
| ECR | `ecr` / `docker` | |
|
|
112
|
+
| ECS | `ecs` / `cluster` / `task` / `tasks` | |
|
|
113
|
+
| EFS | `efs` | |
|
|
114
|
+
| EKS | `eks` | |
|
|
115
|
+
| Elastic Beanstalk | `elasticbeanstalk` / `bean` | |
|
|
116
|
+
| Elastic Load Balancing | `elb` / `alb` | |
|
|
117
|
+
| EventBridge | `eventbridge` / `eb` | |
|
|
118
|
+
| FSx | `fsx` | |
|
|
119
|
+
| Glue | `glue` | |
|
|
120
|
+
| GuardDuty | `guardduty` / `guard` | |
|
|
121
|
+
| IAM | `iam` | |
|
|
122
|
+
| Inspector | `inspector` | |
|
|
123
|
+
| KMS | `kms` | |
|
|
124
|
+
| Lambda | `lambda` | ✓ |
|
|
125
|
+
| Lex | `lex` | |
|
|
126
|
+
| Lightsail | `lightsail` / `ls` | |
|
|
127
|
+
| Marketplace | `marketplace` / `market` / `tennex` | ✓ |
|
|
128
|
+
| Polly | `polly` | |
|
|
129
|
+
| RDS | `rds` | |
|
|
130
|
+
| Route 53 | `route53` / `53` | |
|
|
131
|
+
| S3 | `s3` | |
|
|
132
|
+
| SageMaker | `sagemaker` / `sage` | |
|
|
133
|
+
| Secrets Manager | `secretsmanager` / `secretmanager` / `sm` | |
|
|
134
|
+
| Security groups | `sg` | ✓ |
|
|
135
|
+
| SNS | `sns` | |
|
|
136
|
+
| SQS | `sqs` | |
|
|
137
|
+
| Step Functions | `step` | |
|
|
138
|
+
| Storage Gateway | `storagegateway` / `sgw` | |
|
|
139
|
+
| Support | `support` | |
|
|
140
|
+
| Systems Manager | `ssm` | |
|
|
141
|
+
| SSM Parameter Store | `parameter` / `ps` | ✓ |
|
|
142
|
+
| VPC | `vpc` | |
|
|
143
|
+
| VPC subnets | `subnet` | |
|
|
144
|
+
| VPC network ACLs | `acl` / `nacl` | |
|
|
145
|
+
| VPC Elastic IPs | `elasticip` / `eni` | |
|
|
146
|
+
| VPC route tables | `routetable` / `routes` | |
|
|
147
|
+
| VPC internet gateways | `internetgateway` / `internet` / `igw` | |
|
|
148
|
+
| VPC NAT gateways | `nat` | |
|
|
149
|
+
| VPC endpoints | `endpoints` / `end` | |
|
|
150
|
+
| VPC transit gateways | `transitgateway` / `transit` / `tgw` | |
|
|
151
|
+
|
|
152
|
+
`tennex` opens a Marketplace search for "tennex". Cost Management and Marketplace always open in `us-east-1`.
|
|
153
|
+
|
|
154
|
+
**Supported resource ids:** `i-*` (EC2 instance), `sg-*` (security group), `ami-*`, `lt-*` (launch template), `vol-*` (EBS volume), `snap-*` (EBS snapshot), `eni-*` (network interface), `s3://bucket[/key]`
|
|
155
|
+
|
|
156
|
+
**Supported ARNs:** `ec2` (instance), `lambda` (function), `logs` (log group, log stream), `s3` (bucket, prefix, object)
|
|
157
|
+
|
|
158
|
+
**Cross-account ARNs:** if an ARN's account differs from the current one, `console` switches to the first profile in your AWS config whose `role_arn` is in that account.
|
|
159
|
+
|
|
160
|
+
**Regions:** ids and names are searched across `us-east-1`, `us-east-2`, `us-west-1`, and `us-west-2`, plus the region passed with `-r`.
|
|
161
|
+
|
|
162
|
+
**Session cache:** federated session hashes are cached per profile for 59 minutes in `$XDG_STATE_HOME/console-sessions.json` (default `~/.local/state/console-sessions.json`). Use `-f` to reset it.
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
### `bing-wrapper`
|
|
167
|
+
|
|
168
|
+
A PTY wrapper that runs any command in a pseudo-terminal and plays a macOS system sound when a configurable output pattern is detected, and/or when the command finishes. Useful for long-running CLI tools (e.g. CDK deployments) that prompt for confirmation or take a while to complete.
|
|
169
|
+
|
|
170
|
+
**Requirements:** None
|
|
171
|
+
|
|
172
|
+
**Example Alias Usage:**
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
# wraps cdk to play Frog.aiff when done and play Glass.aiff (default) on "(y/n)" prompts
|
|
176
|
+
alias cdk="bing-wrapper --end --pattern \"(y/n)\" --sound-end /System/Library/Sounds/Frog.aiff -- cdk"
|
|
177
|
+
|
|
178
|
+
# wraps nextflow to play a sound when done
|
|
179
|
+
alias nextflow="bing-wrapper --end -- nextflow"
|
|
180
|
+
|
|
181
|
+
# needed for autocomplete of bing-wrapper and the inner command
|
|
182
|
+
eval "$(bing-wrapper --autocomplete)"
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
**Options:**
|
|
186
|
+
|
|
187
|
+
| Flag | Description | Default |
|
|
188
|
+
| ------------------------ | --------------------------------------------------------- | ------------ |
|
|
189
|
+
| `--end` | Play a sound when the command exits | off |
|
|
190
|
+
| `--pattern <text>` | Play a sound when `<text>` appears in output (repeatable) | none |
|
|
191
|
+
| `--sound-end <path>` | Sound file to play on exit | `Glass.aiff` |
|
|
192
|
+
| `--sound-pattern <path>` | Sound file to play on pattern match | `Glass.aiff` |
|
|
193
|
+
| `--sound-while <path>` | Sound file to play on loop while the command runs | none |
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## Development
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
pip install -e ".[dev]"
|
|
201
|
+
pytest
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Resolver tests live in `tests/console/resolvers/`, one module per service, and use `moto` to mock AWS.
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# Refactor console resolvers into per-service `Resolver` classes
|
|
2
|
+
|
|
3
|
+
## Context
|
|
4
|
+
|
|
5
|
+
The `console` command (entry point `nate2_scripts.console:main`) turns a user argument
|
|
6
|
+
into an AWS console URL and opens it. Today the resolution logic lives in **three
|
|
7
|
+
monolithic resolvers** plus a static service map, each of which handles *every* AWS
|
|
8
|
+
service internally via large `match`/`if` ladders:
|
|
9
|
+
|
|
10
|
+
- [destitationResolver.py](src/nate2_scripts/console/destitationResolver.py) — orchestrator + `_get_services()` static keyword→URL map
|
|
11
|
+
- [arnResolver.py](src/nate2_scripts/console/arnResolver.py) — one class, ~15 `parse_*` methods for every ARN service
|
|
12
|
+
- [nameResolver.py](src/nate2_scripts/console/nameResolver.py) — one class, parallel cross-service/region name lookup
|
|
13
|
+
- [resourceIdResolver.py](src/nate2_scripts/console/resourceIdResolver.py) — one class, `match` over every id prefix
|
|
14
|
+
|
|
15
|
+
The new target shape already exists in [resolvers/s3.py](src/nate2_scripts/console/resolvers/s3.py):
|
|
16
|
+
a `Resolver` ABC ([resolvers/__init__.py](src/nate2_scripts/console/resolvers/__init__.py))
|
|
17
|
+
with four dimensions — `try_resolve_name`, `try_resolve_arn`, `try_resolve_service`,
|
|
18
|
+
`try_resolve_id` — and **one class per service** (`S3Resolver`) implementing all four,
|
|
19
|
+
each returning `None` when the input isn't theirs.
|
|
20
|
+
|
|
21
|
+
Goal: move all existing resolution logic into per-service `Resolver` subclasses following
|
|
22
|
+
the `S3Resolver` pattern, wire a dispatcher that iterates them, and delete the three old
|
|
23
|
+
monolith files. **Functionality stays the same** (same URLs, same parallel name search,
|
|
24
|
+
same priority tie-breaking).
|
|
25
|
+
|
|
26
|
+
## Key design points (settled)
|
|
27
|
+
|
|
28
|
+
- **Granularity:** one class per service, splitting the ec2 namespace into a separate
|
|
29
|
+
`Ec2Resolver` (instances, AMIs, volumes, snapshots, launch templates, ENIs, security
|
|
30
|
+
groups) and `VpcResolver` (vpc, subnet, route table, nat gateway, acl).
|
|
31
|
+
- **Static-only services** (cost, support, athena, ecr, bedrock, config, controltower,
|
|
32
|
+
cognito, cloudfront, route53, kms-link, storagegateway, fsx, ssm, parameter, cloudtrail,
|
|
33
|
+
tennex): each gets its **own** `Resolver` subclass. **No shared mixin/base** — every class
|
|
34
|
+
implements all four methods explicitly, with the three unused ones written out as
|
|
35
|
+
`return None`. `sns`, `sqs`, `ecs`, `eks` (and every other service) stay their own separate
|
|
36
|
+
classes; none are combined.
|
|
37
|
+
- **Name lookup stays parallel + priority-ordered:** logs > s3 > ec2 > cloudformation >
|
|
38
|
+
lambda (existing order in [nameResolver.py:24](src/nate2_scripts/console/nameResolver.py#L24)).
|
|
39
|
+
- **URL wrapping:** resolvers return a **bare host+path** (e.g. `console.aws.amazon.com/s3/...`
|
|
40
|
+
for global, `{region}.console.aws.amazon.com/...` for regional), exactly as `S3Resolver`
|
|
41
|
+
does. The **dispatcher** prepends `https://{mdn}`. `mdn` (multisession domain name) stays
|
|
42
|
+
out of `Context` — it's the dispatcher's concern.
|
|
43
|
+
|
|
44
|
+
## Context object
|
|
45
|
+
|
|
46
|
+
Extend `Context` in [resolvers/__init__.py](src/nate2_scripts/console/resolvers/__init__.py)
|
|
47
|
+
so resolvers have what the monoliths used to pull from constructor args:
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
class Context:
|
|
51
|
+
session: boto3.Session # built from assumed creds in main()
|
|
52
|
+
default_region: str
|
|
53
|
+
regions: list[str] # NEW — from RegionCache, for multi-region name/id search
|
|
54
|
+
current_account: str # for arn.account access checks
|
|
55
|
+
profile: str
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
`session` replaces the bare `boto3.client(...)` calls in the old resolvers — every
|
|
59
|
+
resolver uses `context.session.client(svc, region_name=region)`. In
|
|
60
|
+
[console/__init__.py](src/nate2_scripts/console/__init__.py) `main()`, build the session
|
|
61
|
+
from the `creds` already obtained via `get_credentials()` and populate `Context`.
|
|
62
|
+
|
|
63
|
+
## New files (under `resolvers/`)
|
|
64
|
+
|
|
65
|
+
Resource-resolving services (port the matching `parse_*` / `_resolve_*` bodies verbatim,
|
|
66
|
+
swapping `boto3.client` → `context.session.client` and dropping the `https://{mdn}` prefix
|
|
67
|
+
from returned strings so the dispatcher adds it):
|
|
68
|
+
|
|
69
|
+
| File | Class | Dimensions ported from |
|
|
70
|
+
|---|---|---|
|
|
71
|
+
| `ec2.py` | `Ec2Resolver` | arn `instance`; ids `i-,sg-,ami-,lt-,vol-,snap-,eni-`; name tag:Name; service `ec2`,`sg`,`ami`,`elb`* |
|
|
72
|
+
| `vpc.py` | `VpcResolver` | arn `subnet`,`natgateway`; ids `vpc-,subnet-,rtb-,nat-`; service `vpc`,`subnet`,`acl` |
|
|
73
|
+
| `iam.py` | `IamResolver` | arn `role`,`user`; ids `role/`,`usr/`; service `iam` (global, us-east-1) |
|
|
74
|
+
| `lambda.py` | `LambdaResolver` | arn `function`; name function; service `lambda` |
|
|
75
|
+
| `logs.py` | `LogsResolver` | arn `log-group`; name log-group; service `logs`,`cw`,`cloudwatch` |
|
|
76
|
+
| `cloudformation.py` | `CloudFormationResolver` | name stack; service `cf`,`cloudformation` |
|
|
77
|
+
| `rds.py` | `RdsResolver` | arn `db`,`cluster`; service `rds` |
|
|
78
|
+
| `secretsmanager.py` | `SecretsManagerResolver` | arn `secret`; service `sm`,`secretsmanager`,`secretmanager` |
|
|
79
|
+
| `ecs.py` | `EcsResolver` | arn `cluster`,`service`,`task`,`task-definition`; service `ecs` |
|
|
80
|
+
| `eks.py` | `EksResolver` | arn `cluster`; service `eks` |
|
|
81
|
+
| `sns.py` | `SnsResolver` | arn topic; service `sns` |
|
|
82
|
+
| `sqs.py` | `SqsResolver` | arn queue; service `sqs` |
|
|
83
|
+
| `batch.py` | `BatchResolver` | arn `job-queue`,`compute-environment`,`job-definition`,`job`; service `batch` |
|
|
84
|
+
| `dynamodb.py` | `DynamoDbResolver` | arn `table`; service `dynamo` |
|
|
85
|
+
| `stepfunctions.py` | `StepFunctionsResolver` | arn `stateMachine`,`execution`; service `step` |
|
|
86
|
+
| `elb.py` | `ElbResolver` | arn `loadbalancer`,`targetgroup`; service `elb` |
|
|
87
|
+
|
|
88
|
+
*`elb`/`acl`/`ami`/`sg` service keywords are static list-view links — keep them on the
|
|
89
|
+
resolver that owns that resource where natural, or in a static resolver; either is fine.
|
|
90
|
+
|
|
91
|
+
Static-only resolvers (one class each): `cost.py`, `support.py`, `athena.py`, `ecr.py`,
|
|
92
|
+
`bedrock.py`, `config.py`, `controltower.py`, `cognito.py`, `cloudfront.py`, `route53.py`,
|
|
93
|
+
`kms.py`, `storagegateway.py`, `fsx.py`, `ssm.py`, `cloudtrail.py`, `tennex.py` — seeded
|
|
94
|
+
from the remaining entries of `_get_services()` in
|
|
95
|
+
[destitationResolver.py:60](src/nate2_scripts/console/destitationResolver.py#L60).
|
|
96
|
+
**No shared base/mixin.** Each class implements `try_resolve_service` (matching its own
|
|
97
|
+
keyword aliases) and writes out `try_resolve_name`, `try_resolve_arn`, `try_resolve_id` as
|
|
98
|
+
explicit `return None`.
|
|
99
|
+
|
|
100
|
+
## Dispatcher
|
|
101
|
+
|
|
102
|
+
Rewrite [destitationResolver.py](src/nate2_scripts/console/destitationResolver.py)
|
|
103
|
+
`parse_destination` (keep `DestinationResolver` name + `get_service_list()` so
|
|
104
|
+
[console/__init__.py](src/nate2_scripts/console/__init__.py) callers are untouched) to hold
|
|
105
|
+
an ordered `list[Resolver]` and dispatch:
|
|
106
|
+
|
|
107
|
+
1. Empty arg → `https://{mdn}{default_region}.console.aws.amazon.com/`.
|
|
108
|
+
2. `Arn.try_parse(arg)` succeeds → **ARN input is terminal.** Try each resolver's
|
|
109
|
+
`try_resolve_arn`; return the first non-None. If none resolve it, print an
|
|
110
|
+
ARN-specific error to stderr and `sys.exit(1)` — **do not** fall through to the
|
|
111
|
+
service/id/name steps below.
|
|
112
|
+
3. `try_resolve_service(ctx, arg, search)` across resolvers → first non-None.
|
|
113
|
+
4. Else `try_resolve_id(ctx, arg)` across resolvers → first non-None.
|
|
114
|
+
5. Else **name** search: run each name-capable resolver's `try_resolve_name` concurrently
|
|
115
|
+
(`ThreadPoolExecutor`, reuse `MAX_THREADS`), collect by each resolver's priority index,
|
|
116
|
+
return the lowest-priority winner — preserving [nameResolver.py:33-48](src/nate2_scripts/console/nameResolver.py#L33-L48).
|
|
117
|
+
Each regional resolver loops `context.regions` internally.
|
|
118
|
+
6. None matched → same stderr error + `sys.exit(1)`.
|
|
119
|
+
|
|
120
|
+
Every returned bare path is wrapped as `https://{mdn}{path}` before returning.
|
|
121
|
+
`get_service_list()` returns the union of every resolver's known service keywords (for
|
|
122
|
+
argcomplete in [console/__init__.py:129](src/nate2_scripts/console/__init__.py#L129)).
|
|
123
|
+
|
|
124
|
+
- **`search` is a real second positional CLI arg.** Add a `search` positional (`nargs="?"`,
|
|
125
|
+
default `""`) to `parse_args` in
|
|
126
|
+
[console/__init__.py:115](src/nate2_scripts/console/__init__.py#L115), pass it into
|
|
127
|
+
`parse_destination(service, region, search)`, and thread it through to
|
|
128
|
+
`try_resolve_service` and `try_resolve_name`. Usage becomes `console <service> <search>`
|
|
129
|
+
(e.g. `console ec2 <filter>`).
|
|
130
|
+
- **Apply `search` to the URL when given and the target console supports it.** When
|
|
131
|
+
`search` is non-empty and the resolver's console view accepts a filter/search query
|
|
132
|
+
param, the resolver appends it (URL-encoded) so the console opens pre-filtered — e.g.
|
|
133
|
+
EC2 instances `...#Instances:search=<term>`, Lambda `...#/functions?fo=and&k0=...&v0=<term>`,
|
|
134
|
+
CloudWatch log-groups list, S3 object/bucket search, etc. Where a console view has no such
|
|
135
|
+
param (as noted in the `S3Resolver` comments), the resolver ignores `search` and returns
|
|
136
|
+
the plain URL. Each ported resolver decides per-view; an empty `search` always yields the
|
|
137
|
+
current (unfiltered) URL, preserving today's behavior. The exact query-param syntax per
|
|
138
|
+
service is verified against the live console during implementation.
|
|
139
|
+
- `try_resolve_*` methods **return `None`** on any failure (matching `S3Resolver`), rather
|
|
140
|
+
than `sys.exit` mid-resolve as the old monoliths did. Net effect: the final "not found"
|
|
141
|
+
error is emitted once by the dispatcher. This drops a few service-specific error strings
|
|
142
|
+
(e.g. "current account X cannot access Y") in favor of the unified message — a deliberate
|
|
143
|
+
simplification consistent with the target pattern.
|
|
144
|
+
|
|
145
|
+
## Cleanup
|
|
146
|
+
|
|
147
|
+
Delete [arnResolver.py](src/nate2_scripts/console/arnResolver.py),
|
|
148
|
+
[nameResolver.py](src/nate2_scripts/console/nameResolver.py),
|
|
149
|
+
[resourceIdResolver.py](src/nate2_scripts/console/resourceIdResolver.py) once their logic
|
|
150
|
+
is ported. Move the `split()` helper from arnResolver into `resolvers/__init__.py` (shared
|
|
151
|
+
by several resolvers). Keep `Arn` in `resolvers/__init__.py` as the single ARN parser
|
|
152
|
+
(replaces `ParsedArn` + `split`-based parsing).
|
|
153
|
+
|
|
154
|
+
## Verification
|
|
155
|
+
|
|
156
|
+
No test suite exists, so verify by running the CLI against a real profile:
|
|
157
|
+
|
|
158
|
+
1. `asp <profile>` then reinstall/link: `pip install -e .` (or `uv pip install -e .`).
|
|
159
|
+
2. Exercise each dimension and confirm the same URL opens as before the refactor:
|
|
160
|
+
- **service keyword:** `console s3`, `console ec2`, `console cost`, `console logs`, `console sm`
|
|
161
|
+
- **arn:** `console arn:aws:s3:::<bucket>`, `console arn:aws:lambda:us-east-1:<acct>:function:<fn>`,
|
|
162
|
+
`console arn:aws:iam::<acct>:role/<role>`
|
|
163
|
+
- **resource id:** `console i-<id>`, `console vpc-<id>`, `console subnet-<id>`
|
|
164
|
+
- **name:** `console <bucket-name>`, `console <stack-name>`, `console <lambda-name>`,
|
|
165
|
+
`console <ec2-Name-tag>` — confirm priority tie-break still favors logs > s3 > ec2 > cfn > lambda.
|
|
166
|
+
- **search arg:** `console ec2 <term>` — confirm the second positional is accepted and
|
|
167
|
+
threaded through (no crash; ignored by resolvers that don't use it yet).
|
|
168
|
+
- **empty:** `console` → region landing page.
|
|
169
|
+
- **unknown:** `console zzzzz` → stderr "Unknown service" + exit 1.
|
|
170
|
+
3. Confirm argcomplete still lists services: `console <TAB>` (or inspect `get_service_list()`).
|
|
171
|
+
4. Spot-check that the multisession domain prefix (`{mdn}`) still appears in generated URLs
|
|
172
|
+
when a multi-session hash is cached (the `-f` path vs cached path in
|
|
173
|
+
[console/__init__.py:174-185](src/nate2_scripts/console/__init__.py#L174-L185)).
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# plan
|
|
2
|
+
|
|
3
|
+
I want:
|
|
4
|
+
|
|
5
|
+
- resolve arns
|
|
6
|
+
- tries to switch to account if -s or --switch is specified
|
|
7
|
+
- resolve resource names in
|
|
8
|
+
- resolve service with search parameter
|
|
9
|
+
search parameter is the second arg
|
|
10
|
+
- resolve ids
|
|
11
|
+
|
|
12
|
+
- p or --profile
|
|
13
|
+
- takes a profile glob
|
|
14
|
+
|
|
15
|
+
- f or --force
|
|
16
|
+
- refreshes mdn cache
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "nate2-scripts"
|
|
7
|
-
version = "
|
|
7
|
+
version = "2.0.0"
|
|
8
8
|
description = "Utility scripts for AWS development on macOS"
|
|
9
9
|
requires-python = ">=3.11"
|
|
10
10
|
dependencies = [
|
|
@@ -22,5 +22,18 @@ packages = ["src/nate2_scripts"]
|
|
|
22
22
|
[tool.hatch.build.targets.wheel.force-include]
|
|
23
23
|
"src/nate2_scripts/bing_wrapper/autocomplete.sh" = "nate2_scripts/bing_wrapper/autocomplete.sh"
|
|
24
24
|
|
|
25
|
+
[project.optional-dependencies]
|
|
26
|
+
dev = [
|
|
27
|
+
"boto3-stubs[essential]",
|
|
28
|
+
"moto[all]",
|
|
29
|
+
"pytest",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
[tool.pytest.ini_options]
|
|
33
|
+
testpaths = ["tests"]
|
|
34
|
+
# Resolver test modules are named after the service they cover (s3.py, logs.py,
|
|
35
|
+
# ...) rather than test_*.py, so widen collection to any module under tests/.
|
|
36
|
+
python_files = ["*.py"]
|
|
37
|
+
|
|
25
38
|
[project.urls]
|
|
26
39
|
Homepage = "https://github.com/tennex-nwestfall/nate2-scripts"
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""PTY wrapper for cdk that plays a sound when (y/n) prompts appear."""
|
|
2
2
|
|
|
3
|
+
import argparse
|
|
3
4
|
import fcntl
|
|
4
5
|
import os
|
|
5
6
|
import select
|
|
@@ -10,7 +11,6 @@ import sys
|
|
|
10
11
|
import termios
|
|
11
12
|
import threading
|
|
12
13
|
import tty
|
|
13
|
-
import argparse
|
|
14
14
|
|
|
15
15
|
# _PATTERN = b"(y/n)"
|
|
16
16
|
# _WINDOW = len(_PATTERN) * 2
|
|
@@ -73,7 +73,9 @@ def run(args: argparse.Namespace) -> None:
|
|
|
73
73
|
return
|
|
74
74
|
|
|
75
75
|
if args.sound_while:
|
|
76
|
-
_sound_while_thread = threading.Thread(
|
|
76
|
+
_sound_while_thread = threading.Thread(
|
|
77
|
+
target=_play_sound_while_loop, daemon=True
|
|
78
|
+
)
|
|
77
79
|
_sound_while_thread.start()
|
|
78
80
|
|
|
79
81
|
# Propagate terminal resizes to the child (also sends SIGWINCH to it).
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
# PYTHON_ARGCOMPLETE_OK
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
import subprocess
|
|
7
|
+
import sys
|
|
8
|
+
import time
|
|
9
|
+
import urllib.parse
|
|
10
|
+
import webbrowser
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
import argcomplete
|
|
14
|
+
import boto3
|
|
15
|
+
from argcomplete.completers import ChoicesCompleter
|
|
16
|
+
|
|
17
|
+
from nate2_scripts.console.boto3_session import update_role_names
|
|
18
|
+
from nate2_scripts.console.hash_grabber import HashGrabber
|
|
19
|
+
from nate2_scripts.console.resolvers import Context
|
|
20
|
+
from nate2_scripts.console.session_cache import SessionCache
|
|
21
|
+
from nate2_scripts.console.text_resolver import TextResolver
|
|
22
|
+
|
|
23
|
+
DEFAULT_REGIONS = ["us-east-1", "us-east-2", "us-west-1", "us-west-2"]
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def get_signin_token(session: boto3.Session) -> str:
|
|
27
|
+
creds = session.get_credentials()
|
|
28
|
+
if creds is None:
|
|
29
|
+
print("Error: No credentials found for this session.", file=sys.stderr)
|
|
30
|
+
sys.exit(1)
|
|
31
|
+
frozen = creds.get_frozen_credentials()
|
|
32
|
+
session_json = json.dumps(
|
|
33
|
+
{
|
|
34
|
+
"sessionId": frozen.access_key,
|
|
35
|
+
"sessionKey": frozen.secret_key,
|
|
36
|
+
"sessionToken": frozen.token,
|
|
37
|
+
}
|
|
38
|
+
)
|
|
39
|
+
sess = urllib.parse.quote(session_json)
|
|
40
|
+
token_url = (
|
|
41
|
+
f"https://signin.aws.amazon.com/federation?Action=getSigninToken&Session={sess}"
|
|
42
|
+
)
|
|
43
|
+
result = subprocess.run(
|
|
44
|
+
["curl", "-s", token_url], capture_output=True, text=True, check=True
|
|
45
|
+
)
|
|
46
|
+
try:
|
|
47
|
+
return json.loads(result.stdout)["SigninToken"]
|
|
48
|
+
except json.JSONDecodeError:
|
|
49
|
+
print("Error: Failed to parse JSON response. You may not have credentials.")
|
|
50
|
+
sys.exit(1)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def ensure_profile():
|
|
54
|
+
profile_name = os.environ.get("AWS_PROFILE", "")
|
|
55
|
+
if not profile_name:
|
|
56
|
+
print(
|
|
57
|
+
"Error: AWS_PROFILE environment variable is not set. run asp.",
|
|
58
|
+
file=sys.stderr,
|
|
59
|
+
)
|
|
60
|
+
sys.exit(1)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def parse_args() -> argparse.Namespace:
|
|
64
|
+
parser = argparse.ArgumentParser(
|
|
65
|
+
description="Open AWS console for a specific service."
|
|
66
|
+
)
|
|
67
|
+
svc_arg: Any = parser.add_argument(
|
|
68
|
+
"service",
|
|
69
|
+
type=str,
|
|
70
|
+
default="",
|
|
71
|
+
nargs="?",
|
|
72
|
+
help="The AWS service to open in the console, or an AWS ARN.",
|
|
73
|
+
)
|
|
74
|
+
svc_arg.completer = ChoicesCompleter(TextResolver.get_service_list())
|
|
75
|
+
parser.add_argument(
|
|
76
|
+
"search",
|
|
77
|
+
type=str,
|
|
78
|
+
default=[],
|
|
79
|
+
nargs="*",
|
|
80
|
+
help="Optional search/filter terms applied to service or name resolution. Multiple words are joined with spaces.",
|
|
81
|
+
)
|
|
82
|
+
regions_arg: Any = parser.add_argument(
|
|
83
|
+
"-r",
|
|
84
|
+
"--region",
|
|
85
|
+
type=str,
|
|
86
|
+
default="",
|
|
87
|
+
help="The AWS region to use.",
|
|
88
|
+
)
|
|
89
|
+
regions_arg.completer = argcomplete.completers.ChoicesCompleter(DEFAULT_REGIONS)
|
|
90
|
+
|
|
91
|
+
parser.add_argument(
|
|
92
|
+
"-f",
|
|
93
|
+
"--force",
|
|
94
|
+
action="store_true",
|
|
95
|
+
help="Force new federations. This will reset the federations cache.",
|
|
96
|
+
)
|
|
97
|
+
argcomplete.autocomplete(parser)
|
|
98
|
+
args = parser.parse_intermixed_args()
|
|
99
|
+
# collapse multi-word search into a single string for the resolvers
|
|
100
|
+
args.search = " ".join(args.search)
|
|
101
|
+
|
|
102
|
+
return args
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def get_suffix(
|
|
106
|
+
service: str,
|
|
107
|
+
search: str,
|
|
108
|
+
region: str | None,
|
|
109
|
+
session: boto3.Session,
|
|
110
|
+
) -> tuple[str | None, Context]:
|
|
111
|
+
|
|
112
|
+
# add region if it is not already in the default searchable regions list
|
|
113
|
+
regions = DEFAULT_REGIONS.copy()
|
|
114
|
+
if region:
|
|
115
|
+
session._session.set_config_variable("region", region)
|
|
116
|
+
if region not in regions:
|
|
117
|
+
regions.append(region)
|
|
118
|
+
|
|
119
|
+
# set up context
|
|
120
|
+
context = Context()
|
|
121
|
+
context.session = session
|
|
122
|
+
context.regions = regions
|
|
123
|
+
|
|
124
|
+
# get account data
|
|
125
|
+
context.identity = context.session.client("sts").get_caller_identity()
|
|
126
|
+
|
|
127
|
+
resolver = TextResolver(context)
|
|
128
|
+
|
|
129
|
+
suffix = resolver.get_destination_suffix(service, search)
|
|
130
|
+
return (suffix, context)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def build_signin_url(session: boto3.Session, suffix: str) -> str:
|
|
134
|
+
region = session.region_name
|
|
135
|
+
signin_token = get_signin_token(session)
|
|
136
|
+
dest = urllib.parse.quote(
|
|
137
|
+
f"https://{region}.console.aws.amazon.com{suffix}", safe=""
|
|
138
|
+
)
|
|
139
|
+
return f"https://signin.aws.amazon.com/federation?Action=login&Issuer=&SigninToken={signin_token}&Destination={dest}"
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def build_regular_url(session: boto3.Session, suffix: str, mdn: str) -> str:
|
|
143
|
+
region = session.region_name
|
|
144
|
+
return f"https://{mdn}.{region}.console.aws.amazon.com{suffix}"
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def open(url: str):
|
|
148
|
+
print(f"Opening console URL: {url}")
|
|
149
|
+
webbrowser.open(url)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def open_console(suffix: str, context: Context, session_cache: SessionCache):
|
|
153
|
+
print(f"Opening console with suffix: {suffix}")
|
|
154
|
+
if not context.useprefix:
|
|
155
|
+
open(f"https://{suffix}")
|
|
156
|
+
else:
|
|
157
|
+
hash = session_cache.get_session_hash(context.session.profile_name)
|
|
158
|
+
if hash is None:
|
|
159
|
+
print(
|
|
160
|
+
f"{context.session.profile_name} has no cached session. Grabbing signin token."
|
|
161
|
+
)
|
|
162
|
+
url = build_signin_url(context.session, suffix)
|
|
163
|
+
open(url)
|
|
164
|
+
hash = HashGrabber.get_hash(context.identity["Account"], time.time())
|
|
165
|
+
if hash is None:
|
|
166
|
+
print("Failed to grab hash.")
|
|
167
|
+
sys.exit(1)
|
|
168
|
+
print(
|
|
169
|
+
f"Successfully grabbed hash for {context.session.profile_name}: {hash}"
|
|
170
|
+
)
|
|
171
|
+
session_cache.record_valid_hash(context.session.profile_name, hash)
|
|
172
|
+
|
|
173
|
+
else:
|
|
174
|
+
multisession_domain_name = f"{context.identity['Account']}-{hash}"
|
|
175
|
+
print(
|
|
176
|
+
f"{context.session.profile_name} cache has cached multi session with domain: {multisession_domain_name}"
|
|
177
|
+
)
|
|
178
|
+
url = build_regular_url(context.session, suffix, multisession_domain_name)
|
|
179
|
+
open(url)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def main() -> None:
|
|
183
|
+
ensure_profile()
|
|
184
|
+
args = parse_args()
|
|
185
|
+
|
|
186
|
+
session = boto3.Session()
|
|
187
|
+
session_cache = SessionCache()
|
|
188
|
+
update_role_names(session)
|
|
189
|
+
|
|
190
|
+
if args.force:
|
|
191
|
+
print("Force passed. Resetting session cache.")
|
|
192
|
+
session_cache.reset()
|
|
193
|
+
|
|
194
|
+
print("Default region is: ", session.region_name)
|
|
195
|
+
|
|
196
|
+
suffix, context = get_suffix(args.service, args.search, args.region, session)
|
|
197
|
+
if suffix:
|
|
198
|
+
open_console(suffix, context, session_cache)
|
|
199
|
+
else:
|
|
200
|
+
print("Cannot open console page.")
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
if __name__ == "__main__":
|
|
204
|
+
main()
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
# set user to OrganizationAccountAccessRole/nwestfall@tennex.io
|