stackport 0.4.8__tar.gz → 0.4.9__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.
- {stackport-0.4.8/stackport.egg-info → stackport-0.4.9}/PKG-INFO +4 -4
- {stackport-0.4.8 → stackport-0.4.9}/README.md +1 -1
- {stackport-0.4.8 → stackport-0.4.9}/backend/aws_client.py +8 -0
- {stackport-0.4.8 → stackport-0.4.9}/backend/cli.py +14 -65
- stackport-0.4.9/backend/downloads.py +64 -0
- {stackport-0.4.8 → stackport-0.4.9}/backend/learn/verify.py +2 -7
- {stackport-0.4.8 → stackport-0.4.9}/backend/main.py +16 -2
- {stackport-0.4.8 → stackport-0.4.9}/backend/routes/dynamodb.py +184 -171
- {stackport-0.4.8 → stackport-0.4.9}/backend/routes/iam.py +17 -15
- {stackport-0.4.8 → stackport-0.4.9}/backend/routes/lambda_svc.py +8 -35
- {stackport-0.4.8 → stackport-0.4.9}/backend/routes/logs.py +27 -30
- {stackport-0.4.8 → stackport-0.4.9}/backend/routes/resources.py +123 -96
- {stackport-0.4.8 → stackport-0.4.9}/backend/routes/s3.py +49 -14
- {stackport-0.4.8 → stackport-0.4.9}/backend/routes/sqs.py +48 -31
- {stackport-0.4.8 → stackport-0.4.9}/backend/routes/stats.py +51 -11
- {stackport-0.4.8 → stackport-0.4.9}/backend/routes/tags.py +51 -20
- {stackport-0.4.8 → stackport-0.4.9}/backend/schemas/sqs.py +35 -23
- {stackport-0.4.8 → stackport-0.4.9}/backend/websocket.py +2 -11
- {stackport-0.4.8 → stackport-0.4.9}/pyproject.toml +3 -3
- {stackport-0.4.8 → stackport-0.4.9/stackport.egg-info}/PKG-INFO +4 -4
- {stackport-0.4.8 → stackport-0.4.9}/stackport.egg-info/SOURCES.txt +32 -26
- {stackport-0.4.8 → stackport-0.4.9}/stackport.egg-info/requires.txt +2 -2
- stackport-0.4.9/tests/test_aws_client_checksums.py +77 -0
- {stackport-0.4.8 → stackport-0.4.9}/tests/test_dynamodb_routes.py +281 -9
- stackport-0.4.9/tests/test_generic_resources.py +514 -0
- {stackport-0.4.8 → stackport-0.4.9}/tests/test_iam_routes.py +100 -181
- {stackport-0.4.8 → stackport-0.4.9}/tests/test_logs_routes.py +87 -2
- {stackport-0.4.8 → stackport-0.4.9}/tests/test_logs_tail.py +2 -1
- {stackport-0.4.8 → stackport-0.4.9}/tests/test_registries.py +32 -1
- {stackport-0.4.8 → stackport-0.4.9}/tests/test_request_safety.py +32 -0
- {stackport-0.4.8 → stackport-0.4.9}/tests/test_s3_routes.py +137 -152
- stackport-0.4.9/tests/test_s3_transfers.py +285 -0
- {stackport-0.4.8 → stackport-0.4.9}/tests/test_sqs_routes.py +162 -0
- stackport-0.4.9/tests/test_suite_isolation.py +41 -0
- stackport-0.4.9/tests/test_tags_roundtrip.py +409 -0
- stackport-0.4.8/ui/dist/assets/CloudscapeAbout-ykzJ5x3Y.js → stackport-0.4.9/ui/dist/assets/CloudscapeAbout-C4S2ahmA.js +1 -1
- stackport-0.4.8/ui/dist/assets/CloudscapeDashboard-CJt6f1T3.js → stackport-0.4.9/ui/dist/assets/CloudscapeDashboard-CgPMF-G_.js +1 -1
- stackport-0.4.8/ui/dist/assets/CloudscapeLearn-l4d6rjUk.js → stackport-0.4.9/ui/dist/assets/CloudscapeLearn-B8fMSiDF.js +1 -1
- stackport-0.4.9/ui/dist/assets/CloudscapeResourceBrowser-IhkIJ1Hg.js +19 -0
- stackport-0.4.8/ui/dist/assets/CloudscapeSettings-Hg6Upkx0.js → stackport-0.4.9/ui/dist/assets/CloudscapeSettings-D4ZhF-Od.js +1 -1
- stackport-0.4.8/ui/dist/assets/CloudscapeShell-S38ixn79.js → stackport-0.4.9/ui/dist/assets/CloudscapeShell-C7gsElS6.js +1 -1
- stackport-0.4.8/ui/dist/assets/ServiceStatus-BHSv7SA3.js → stackport-0.4.9/ui/dist/assets/ServiceStatus-Bn8D3Whz.js +1 -1
- stackport-0.4.8/ui/dist/assets/StateMachineGraph-DUiGayyA.js → stackport-0.4.9/ui/dist/assets/StateMachineGraph-DBZaV-ye.js +1 -1
- stackport-0.4.8/ui/dist/assets/index-v1JyxN8N.js → stackport-0.4.9/ui/dist/assets/index-BIP6RNbD.js +1 -1
- stackport-0.4.8/ui/dist/assets/index-ClXofh-m.js → stackport-0.4.9/ui/dist/assets/index-BOjNdz1a.js +1 -1
- stackport-0.4.8/ui/dist/assets/index-SWJZXC-H.js → stackport-0.4.9/ui/dist/assets/index-C56KgCLT.js +1 -1
- stackport-0.4.8/ui/dist/assets/index-DEgNFpXr.js → stackport-0.4.9/ui/dist/assets/index-R-ZHGMoF.js +1 -1
- stackport-0.4.8/ui/dist/assets/index-CTGN6ph7.js → stackport-0.4.9/ui/dist/assets/index-bfBTZg8t.js +7 -7
- {stackport-0.4.8 → stackport-0.4.9}/ui/dist/index.html +1 -1
- stackport-0.4.8/ui/dist/assets/CloudscapeResourceBrowser-DETT9g8r.js +0 -19
- {stackport-0.4.8 → stackport-0.4.9}/LICENSE +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/MANIFEST.in +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/backend/__init__.py +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/backend/aws_emulator.py +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/backend/aws_errors.py +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/backend/cache.py +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/backend/config.py +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/backend/connection.py +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/backend/emulators.py +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/backend/endpoint_store.py +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/backend/error_handlers.py +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/backend/learn/__init__.py +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/backend/learn/cli_setup.py +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/backend/learn/content.py +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/backend/learn/hotspots.py +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/backend/learn/trails/aws-first-steps.json +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/backend/learn_store.py +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/backend/origins.py +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/backend/routes/__init__.py +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/backend/routes/common.py +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/backend/routes/ec2.py +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/backend/routes/endpoints.py +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/backend/routes/kms.py +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/backend/routes/learn.py +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/backend/routes/monitoring.py +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/backend/routes/rds.py +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/backend/routes/secretsmanager.py +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/backend/routes/sns.py +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/backend/routes/stepfunctions.py +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/backend/schemas/__init__.py +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/backend/schemas/dynamodb.py +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/backend/schemas/endpoints.py +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/backend/schemas/lambda_svc.py +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/backend/schemas/learn.py +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/backend/schemas/logs.py +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/backend/schemas/monitoring.py +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/backend/schemas/s3.py +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/backend/schemas/secretsmanager.py +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/backend/schemas/sns.py +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/backend/schemas/stepfunctions.py +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/backend/schemas/tags.py +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/setup.cfg +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/stackport.egg-info/dependency_links.txt +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/stackport.egg-info/entry_points.txt +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/stackport.egg-info/top_level.txt +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/tests/test_aws_client_tls.py +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/tests/test_aws_emulator.py +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/tests/test_aws_errors.py +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/tests/test_cache.py +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/tests/test_cli.py +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/tests/test_client.py +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/tests/test_config.py +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/tests/test_connection.py +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/tests/test_create_resources.py +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/tests/test_ec2_routes.py +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/tests/test_emulators.py +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/tests/test_endpoint_store.py +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/tests/test_endpoint_store_env.py +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/tests/test_endpoints.py +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/tests/test_endpoints_tls.py +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/tests/test_error_handlers.py +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/tests/test_kms_routes.py +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/tests/test_lambda_code_download.py +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/tests/test_lambda_routes.py +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/tests/test_learn_cli_setup.py +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/tests/test_learn_content.py +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/tests/test_learn_routes.py +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/tests/test_learn_store.py +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/tests/test_monitoring_routes.py +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/tests/test_multi_endpoint.py +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/tests/test_rds_routes.py +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/tests/test_readonly_middleware.py +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/tests/test_routes.py +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/tests/test_s3_upload_limit_env.py +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/tests/test_secretsmanager_routes.py +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/tests/test_sns_routes.py +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/tests/test_stats_probe.py +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/tests/test_tags_routes.py +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/tests/test_websocket.py +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/ui/dist/assets/CloudscapeResourceBrowser-BR6m7Ajg.css +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/ui/dist/assets/CloudscapeSettings-DI0pOcAA.css +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/ui/dist/assets/CloudscapeShell-oIMuVycJ.css +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/ui/dist/assets/ServiceStatus-DNBdeKId.css +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/ui/dist/assets/index-DKIdXFX0.css +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/ui/dist/assets/index-D__irddE.css +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/ui/dist/assets/index-DoY0HscW.css +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/ui/dist/assets/index-RwoKZF4y.css +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/ui/dist/assets/stepfunctions-graph-B6nvU4OB.js +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/ui/dist/assets/utils-Dm4tJbJR.js +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/ui/dist/aws-icons/acm.svg +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/ui/dist/aws-icons/apigateway.svg +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/ui/dist/aws-icons/appsync.svg +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/ui/dist/aws-icons/athena.svg +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/ui/dist/aws-icons/cloudformation.svg +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/ui/dist/aws-icons/cloudfront.svg +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/ui/dist/aws-icons/cognito-idp.svg +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/ui/dist/aws-icons/dynamodb.svg +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/ui/dist/aws-icons/ec2.svg +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/ui/dist/aws-icons/ecr.svg +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/ui/dist/aws-icons/ecs.svg +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/ui/dist/aws-icons/elasticache.svg +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/ui/dist/aws-icons/elasticfilesystem.svg +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/ui/dist/aws-icons/elasticloadbalancing.svg +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/ui/dist/aws-icons/elasticmapreduce.svg +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/ui/dist/aws-icons/events.svg +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/ui/dist/aws-icons/firehose.svg +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/ui/dist/aws-icons/glue.svg +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/ui/dist/aws-icons/iam.svg +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/ui/dist/aws-icons/kinesis.svg +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/ui/dist/aws-icons/kms.svg +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/ui/dist/aws-icons/lambda.svg +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/ui/dist/aws-icons/logs.svg +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/ui/dist/aws-icons/monitoring.svg +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/ui/dist/aws-icons/rds.svg +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/ui/dist/aws-icons/route53.svg +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/ui/dist/aws-icons/s3.svg +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/ui/dist/aws-icons/secretsmanager.svg +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/ui/dist/aws-icons/ses.svg +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/ui/dist/aws-icons/sns.svg +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/ui/dist/aws-icons/sqs.svg +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/ui/dist/aws-icons/ssm.svg +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/ui/dist/aws-icons/stepfunctions.svg +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/ui/dist/aws-icons/wafv2.svg +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/ui/dist/favicon.png +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/ui/dist/favicon.svg +0 -0
- {stackport-0.4.8 → stackport-0.4.9}/ui/dist/stackport-logo.svg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: stackport
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.9
|
|
4
4
|
Summary: Universal AWS resource browser for local emulators
|
|
5
5
|
Author: Davi Reis Vieira
|
|
6
6
|
License: MIT
|
|
@@ -23,7 +23,7 @@ Description-Content-Type: text/markdown
|
|
|
23
23
|
License-File: LICENSE
|
|
24
24
|
Requires-Dist: fastapi>=0.115.0
|
|
25
25
|
Requires-Dist: uvicorn>=0.30.0
|
|
26
|
-
Requires-Dist: boto3>=1.
|
|
26
|
+
Requires-Dist: boto3>=1.36.0
|
|
27
27
|
Requires-Dist: click>=8.0
|
|
28
28
|
Requires-Dist: websockets>=12.0
|
|
29
29
|
Requires-Dist: python-multipart>=0.0.26
|
|
@@ -31,7 +31,7 @@ Provides-Extra: dev
|
|
|
31
31
|
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
32
32
|
Requires-Dist: pytest-mock>=3.12.0; extra == "dev"
|
|
33
33
|
Requires-Dist: httpx>=0.27.0; extra == "dev"
|
|
34
|
-
Requires-Dist: moto[dynamodb,iam,lambda,s3,sqs]>=5.0; extra == "dev"
|
|
34
|
+
Requires-Dist: moto[apigateway,apigatewayv2,cognitoidp,dynamodb,glue,iam,lambda,s3,sqs]>=5.0; extra == "dev"
|
|
35
35
|
Dynamic: license-file
|
|
36
36
|
|
|
37
37
|
<p align="center">
|
|
@@ -249,7 +249,7 @@ You can test a connection before saving it with the "Test Connection" button in
|
|
|
249
249
|
| Service | Browse | Write operations |
|
|
250
250
|
|---------|--------|------------------|
|
|
251
251
|
| **S3** | Buckets, objects, folder navigation, search | Create buckets and folders, upload, download, delete, batch delete; edit versioning, lifecycle, notifications, CORS, tags |
|
|
252
|
-
| **DynamoDB** | Tables, schema, items, scan and query | Create tables, add, edit and delete items, batch write |
|
|
252
|
+
| **DynamoDB** | Tables, schema, items, scan and query, live item count | Create tables, add, edit and delete items, batch write |
|
|
253
253
|
| **Lambda** | Functions, config, aliases, versions, event sources | Invoke with a JSON payload, edit configuration, download code |
|
|
254
254
|
| **SQS** | Queues, messages, attributes | Create and delete queues, send (single or batch), receive, delete messages, purge, edit attributes and redrive policy |
|
|
255
255
|
| **SNS** | Topics, subscriptions | Create and delete topics, subscribe, unsubscribe, publish |
|
|
@@ -213,7 +213,7 @@ You can test a connection before saving it with the "Test Connection" button in
|
|
|
213
213
|
| Service | Browse | Write operations |
|
|
214
214
|
|---------|--------|------------------|
|
|
215
215
|
| **S3** | Buckets, objects, folder navigation, search | Create buckets and folders, upload, download, delete, batch delete; edit versioning, lifecycle, notifications, CORS, tags |
|
|
216
|
-
| **DynamoDB** | Tables, schema, items, scan and query | Create tables, add, edit and delete items, batch write |
|
|
216
|
+
| **DynamoDB** | Tables, schema, items, scan and query, live item count | Create tables, add, edit and delete items, batch write |
|
|
217
217
|
| **Lambda** | Functions, config, aliases, versions, event sources | Invoke with a JSON payload, edit configuration, download code |
|
|
218
218
|
| **SQS** | Queues, messages, attributes | Create and delete queues, send (single or batch), receive, delete messages, purge, edit attributes and redrive policy |
|
|
219
219
|
| **SNS** | Topics, subscriptions | Create and delete topics, subscribe, unsubscribe, publish |
|
|
@@ -26,10 +26,18 @@ _UNSET = object()
|
|
|
26
26
|
# dozens of services. Locally nothing is throttled, a refused connection or a
|
|
27
27
|
# rejected certificate won't fix itself on retry, and the UI polls again in
|
|
28
28
|
# seconds, so fail fast instead. STACKPORT_PROBE_TIMEOUT bounds the connect.
|
|
29
|
+
# - Flexible checksums (CRC32 on uploads, checked on downloads) only where an API
|
|
30
|
+
# requires them, as before botocore 1.36. Emulators keep them unevenly: moto
|
|
31
|
+
# returns a multipart object's checksum of part checksums as if it covered the
|
|
32
|
+
# whole object, so a download of it failed its check. Without asking for them,
|
|
33
|
+
# StackPort's multipart uploads don't leave such objects behind for other
|
|
34
|
+
# clients either.
|
|
29
35
|
_LOCAL_ENDPOINT_CONFIG = Config(
|
|
30
36
|
inject_host_prefix=False,
|
|
31
37
|
connect_timeout=STACKPORT_PROBE_TIMEOUT,
|
|
32
38
|
retries={"mode": "standard", "total_max_attempts": 1},
|
|
39
|
+
request_checksum_calculation="when_required",
|
|
40
|
+
response_checksum_validation="when_required",
|
|
33
41
|
)
|
|
34
42
|
|
|
35
43
|
# Last stat stamp seen per AWS Emulator files directory, to notice restarts.
|
|
@@ -22,12 +22,13 @@ from backend.config import (
|
|
|
22
22
|
from backend.connection import EndpointInfo, InvalidEndpointUrl
|
|
23
23
|
from backend.routes.common import UnknownEndpoint, endpoint_info_for
|
|
24
24
|
from backend.routes.resources import (
|
|
25
|
-
DESCRIBE_REGISTRY,
|
|
26
25
|
_PREFERRED_ID_FIELD,
|
|
26
|
+
UnknownResourceType,
|
|
27
27
|
_extract_id,
|
|
28
28
|
_serialize,
|
|
29
|
+
describe_resource,
|
|
29
30
|
)
|
|
30
|
-
from backend.routes.stats import SERVICE_REGISTRY,
|
|
31
|
+
from backend.routes.stats import SERVICE_REGISTRY, _probe_service, list_items
|
|
31
32
|
|
|
32
33
|
logger = logging.getLogger(__name__)
|
|
33
34
|
|
|
@@ -149,14 +150,7 @@ def list(service, endpoint, region, output):
|
|
|
149
150
|
for resource_type, boto3_service, method_name, response_key in registry_entries:
|
|
150
151
|
try:
|
|
151
152
|
client = get_client(boto3_service, **client_kwargs)
|
|
152
|
-
|
|
153
|
-
kwargs = _METHOD_KWARGS.get((boto3_service, method_name), {})
|
|
154
|
-
resp = method(**kwargs)
|
|
155
|
-
items = resp.get(response_key, [])
|
|
156
|
-
# Handle nested structures (e.g., cloudfront DistributionList.Items)
|
|
157
|
-
if isinstance(items, dict) and "Items" in items:
|
|
158
|
-
items = items.get("Items", []) or []
|
|
159
|
-
resources[resource_type] = items
|
|
153
|
+
resources[resource_type] = list_items(client, boto3_service, method_name, response_key)
|
|
160
154
|
except Exception as exc:
|
|
161
155
|
logger.debug("Failed to list %s/%s: %s", service, resource_type, exc, exc_info=True)
|
|
162
156
|
resources[resource_type] = []
|
|
@@ -207,61 +201,16 @@ def describe(service, resource_type, resource_id, endpoint, region, output):
|
|
|
207
201
|
"""Describe a specific resource."""
|
|
208
202
|
client_kwargs = _client_kwargs(endpoint, region)
|
|
209
203
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
resp.pop("ResponseMetadata", None)
|
|
221
|
-
detail = _serialize(resp.get("WebACL", resp))
|
|
222
|
-
except Exception as exc:
|
|
223
|
-
click.echo(f"Error describing {service}/{resource_type}/{resource_id}: {exc}", err=True)
|
|
224
|
-
sys.exit(1)
|
|
225
|
-
else:
|
|
226
|
-
lookup = DESCRIBE_REGISTRY.get((service, resource_type))
|
|
227
|
-
if not lookup:
|
|
228
|
-
click.echo(f"Error: No detail lookup registered for {service}/{resource_type}", err=True)
|
|
229
|
-
sys.exit(1)
|
|
230
|
-
|
|
231
|
-
boto3_service, method_name, id_param, response_key = lookup
|
|
232
|
-
|
|
233
|
-
# Some APIs take list parameters
|
|
234
|
-
_LIST_PARAMS = {
|
|
235
|
-
"InstanceIds",
|
|
236
|
-
"VpcIds",
|
|
237
|
-
"SubnetIds",
|
|
238
|
-
"GroupIds",
|
|
239
|
-
"VolumeIds",
|
|
240
|
-
"repositoryNames",
|
|
241
|
-
"clusters",
|
|
242
|
-
"AlarmNames",
|
|
243
|
-
"LoadBalancerArns",
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
try:
|
|
247
|
-
client = get_client(boto3_service, **client_kwargs)
|
|
248
|
-
method = getattr(client, method_name)
|
|
249
|
-
if id_param in _LIST_PARAMS:
|
|
250
|
-
resp = method(**{id_param: [resource_id]})
|
|
251
|
-
else:
|
|
252
|
-
resp = method(**{id_param: resource_id})
|
|
253
|
-
|
|
254
|
-
resp.pop("ResponseMetadata", None)
|
|
255
|
-
|
|
256
|
-
if response_key is not None:
|
|
257
|
-
detail = resp.get(response_key, resp)
|
|
258
|
-
else:
|
|
259
|
-
detail = resp
|
|
260
|
-
|
|
261
|
-
detail = _serialize(detail)
|
|
262
|
-
except Exception as exc:
|
|
263
|
-
click.echo(f"Error describing {service}/{resource_type}/{resource_id}: {exc}", err=True)
|
|
264
|
-
sys.exit(1)
|
|
204
|
+
try:
|
|
205
|
+
detail = describe_resource(
|
|
206
|
+
lambda boto3_service: get_client(boto3_service, **client_kwargs), service, resource_type, resource_id
|
|
207
|
+
)
|
|
208
|
+
except UnknownResourceType as exc:
|
|
209
|
+
click.echo(f"Error: {exc}", err=True)
|
|
210
|
+
sys.exit(1)
|
|
211
|
+
except Exception as exc:
|
|
212
|
+
click.echo(f"Error describing {service}/{resource_type}/{resource_id}: {exc}", err=True)
|
|
213
|
+
sys.exit(1)
|
|
265
214
|
|
|
266
215
|
if output == "json":
|
|
267
216
|
click.echo(json.dumps(detail, indent=2))
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""Passing a download from an endpoint on to the browser: S3 objects, Lambda code packages."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
from collections.abc import Iterable, Mapping
|
|
7
|
+
from typing import Protocol
|
|
8
|
+
from urllib.parse import quote
|
|
9
|
+
|
|
10
|
+
from fastapi.responses import StreamingResponse
|
|
11
|
+
from starlette.types import Receive, Scope, Send
|
|
12
|
+
|
|
13
|
+
_PLAIN_FILENAME = re.compile(r"[A-Za-z0-9._-]+")
|
|
14
|
+
|
|
15
|
+
# How much of the upstream body to read for each chunk sent on. Reading a sync
|
|
16
|
+
# body costs a hop through the thread pool per chunk, so tiny chunks make a
|
|
17
|
+
# download crawl; at this size, relaying keeps pace with reading directly.
|
|
18
|
+
RELAY_CHUNK_BYTES = 64 * 1024
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Closeable(Protocol):
|
|
22
|
+
def close(self) -> None: ...
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def attachment(filename: str) -> str:
|
|
26
|
+
"""A Content-Disposition value that saves the download as ``filename`` (RFC 6266).
|
|
27
|
+
|
|
28
|
+
A name outside a conservative ASCII set gets an underscored fallback plus the
|
|
29
|
+
exact name, percent-encoded as UTF-8, in ``filename*`` (RFC 8187), so quotes,
|
|
30
|
+
backslashes, semicolons, line breaks and characters outside Latin-1 can't
|
|
31
|
+
break the header. The fallback replaces quotes and backslashes rather than
|
|
32
|
+
escaping them, since some browsers don't unescape them (RFC 6266, appendix D).
|
|
33
|
+
"""
|
|
34
|
+
if _PLAIN_FILENAME.fullmatch(filename):
|
|
35
|
+
return f'attachment; filename="{filename}"'
|
|
36
|
+
fallback = re.sub(r"[^A-Za-z0-9._-]", "_", filename)
|
|
37
|
+
return f"attachment; filename=\"{fallback}\"; filename*=UTF-8''{quote(filename, safe='')}"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class RelayResponse(StreamingResponse):
|
|
41
|
+
"""Streams a body read from an open upstream response, and closes the upstream however the response ends.
|
|
42
|
+
|
|
43
|
+
When the client goes away, Starlette stops iterating the body without
|
|
44
|
+
closing it, so the body iterator can't be the one that closes the upstream.
|
|
45
|
+
An error while reading the upstream propagates, so the server drops the
|
|
46
|
+
connection instead of finishing a cut-off download that would look complete.
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
def __init__(
|
|
50
|
+
self,
|
|
51
|
+
content: Iterable[bytes],
|
|
52
|
+
*,
|
|
53
|
+
upstream: Closeable,
|
|
54
|
+
media_type: str,
|
|
55
|
+
headers: Mapping[str, str],
|
|
56
|
+
):
|
|
57
|
+
super().__init__(content, media_type=media_type, headers=headers)
|
|
58
|
+
self._upstream = upstream
|
|
59
|
+
|
|
60
|
+
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
|
|
61
|
+
try:
|
|
62
|
+
await super().__call__(scope, receive, send)
|
|
63
|
+
finally:
|
|
64
|
+
self._upstream.close()
|
|
@@ -98,7 +98,7 @@ def _dynamodb_item_count(ep: EndpointInfo, params: dict) -> dict:
|
|
|
98
98
|
|
|
99
99
|
def _resource_exists(ep: EndpointInfo, params: dict) -> dict:
|
|
100
100
|
"""Generic existence check driven by SERVICE_REGISTRY (no cache)."""
|
|
101
|
-
from backend.routes.stats import
|
|
101
|
+
from backend.routes.stats import SERVICE_REGISTRY, list_items
|
|
102
102
|
|
|
103
103
|
service = params["service"]
|
|
104
104
|
resource_type = params["resource_type"]
|
|
@@ -108,12 +108,7 @@ def _resource_exists(ep: EndpointInfo, params: dict) -> dict:
|
|
|
108
108
|
if rtype != resource_type:
|
|
109
109
|
continue
|
|
110
110
|
client = get_client(boto_service, **ep.client_kwargs())
|
|
111
|
-
|
|
112
|
-
response = getattr(client, method)(**kwargs)
|
|
113
|
-
items = response.get(response_key, [])
|
|
114
|
-
if isinstance(items, dict) and "Items" in items:
|
|
115
|
-
items = items["Items"]
|
|
116
|
-
for item in items:
|
|
111
|
+
for item in list_items(client, boto_service, method, response_key):
|
|
117
112
|
if isinstance(item, str) and target in item:
|
|
118
113
|
return {"passed": True, "message": f"Found {resource_type[:-1] if resource_type.endswith('s') else resource_type} '{target}'."}
|
|
119
114
|
if isinstance(item, dict) and any(target in str(v) for v in item.values() if isinstance(v, str)):
|
|
@@ -8,6 +8,7 @@ from fastapi import FastAPI, Request, WebSocket
|
|
|
8
8
|
from fastapi.middleware.cors import CORSMiddleware
|
|
9
9
|
from fastapi.responses import FileResponse, JSONResponse
|
|
10
10
|
from fastapi.staticfiles import StaticFiles
|
|
11
|
+
from starlette.convertors import PathConvertor, register_url_convertor
|
|
11
12
|
from starlette.types import ASGIApp, Receive, Scope, Send
|
|
12
13
|
|
|
13
14
|
from botocore.exceptions import (
|
|
@@ -175,14 +176,27 @@ async def ws_logs_tail(websocket: WebSocket):
|
|
|
175
176
|
await logs_tail_endpoint(websocket)
|
|
176
177
|
|
|
177
178
|
|
|
178
|
-
|
|
179
|
+
class AppPagePath(PathConvertor):
|
|
180
|
+
"""A path the SPA fallback answers: anything except /api, /ws and paths under them.
|
|
181
|
+
|
|
182
|
+
Those never reach the fallback, so an unknown API path gets the same JSON
|
|
183
|
+
404 (or 405 for a known path and another method) as when the UI isn't
|
|
184
|
+
built, rather than the app page with a 200.
|
|
185
|
+
"""
|
|
186
|
+
|
|
187
|
+
regex = r"(?!(?:api|ws)(?:/|$)).*"
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
register_url_convertor("app_page", AppPagePath())
|
|
191
|
+
|
|
192
|
+
# Serve UI static files — mount assets under /assets, SPA fallback for every other page
|
|
179
193
|
ui_dist = os.path.join(os.path.dirname(__file__), "..", "ui", "dist")
|
|
180
194
|
if os.path.isdir(ui_dist):
|
|
181
195
|
app.mount("/assets", StaticFiles(directory=os.path.join(ui_dist, "assets")), name="assets")
|
|
182
196
|
|
|
183
197
|
ui_root = os.path.realpath(ui_dist)
|
|
184
198
|
|
|
185
|
-
@app.get("/{path:
|
|
199
|
+
@app.get("/{path:app_page}")
|
|
186
200
|
def spa_fallback(path: str):
|
|
187
201
|
# Serve a file from ui/dist when the path names one inside it. The path
|
|
188
202
|
# comes from the URL, where %2F decodes to "/", so resolve it and check
|