stackport 0.3.4__tar.gz → 0.3.7__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.3.4/stackport.egg-info → stackport-0.3.7}/PKG-INFO +2 -1
- {stackport-0.3.4 → stackport-0.3.7}/backend/main.py +3 -2
- {stackport-0.3.4 → stackport-0.3.7}/backend/routes/ec2.py +199 -0
- stackport-0.3.7/backend/routes/rds.py +447 -0
- {stackport-0.3.4 → stackport-0.3.7}/pyproject.toml +2 -2
- {stackport-0.3.4 → stackport-0.3.7/stackport.egg-info}/PKG-INFO +2 -1
- {stackport-0.3.4 → stackport-0.3.7}/stackport.egg-info/SOURCES.txt +8 -6
- {stackport-0.3.4 → stackport-0.3.7}/stackport.egg-info/requires.txt +1 -0
- stackport-0.3.7/tests/test_ec2_routes.py +637 -0
- stackport-0.3.7/tests/test_rds_routes.py +554 -0
- stackport-0.3.4/ui/dist/assets/StateMachineGraph-B8vy_SNe.js → stackport-0.3.7/ui/dist/assets/StateMachineGraph-C3a2WdTx.js +1 -1
- stackport-0.3.7/ui/dist/assets/index-B-seC0pm.js +652 -0
- stackport-0.3.7/ui/dist/assets/index-BZr5kWvs.css +1 -0
- {stackport-0.3.4 → stackport-0.3.7}/ui/dist/index.html +2 -2
- stackport-0.3.4/tests/test_ec2_routes.py +0 -298
- stackport-0.3.4/ui/dist/assets/index-BoJK2una.css +0 -1
- stackport-0.3.4/ui/dist/assets/index-DsnQ23fm.js +0 -652
- {stackport-0.3.4 → stackport-0.3.7}/LICENSE +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/MANIFEST.in +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/README.md +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/backend/__init__.py +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/backend/aws_client.py +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/backend/cache.py +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/backend/cli.py +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/backend/config.py +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/backend/endpoint_store.py +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/backend/routes/__init__.py +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/backend/routes/common.py +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/backend/routes/dynamodb.py +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/backend/routes/endpoints.py +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/backend/routes/iam.py +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/backend/routes/lambda_svc.py +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/backend/routes/logs.py +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/backend/routes/resources.py +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/backend/routes/s3.py +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/backend/routes/secretsmanager.py +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/backend/routes/sqs.py +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/backend/routes/stats.py +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/backend/routes/stepfunctions.py +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/backend/routes/tags.py +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/backend/schemas/__init__.py +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/backend/schemas/dynamodb.py +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/backend/schemas/endpoints.py +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/backend/schemas/lambda_svc.py +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/backend/schemas/s3.py +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/backend/schemas/secretsmanager.py +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/backend/schemas/sqs.py +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/backend/schemas/stepfunctions.py +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/backend/schemas/tags.py +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/backend/websocket.py +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/setup.cfg +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/stackport.egg-info/dependency_links.txt +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/stackport.egg-info/entry_points.txt +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/stackport.egg-info/top_level.txt +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/tests/test_cache.py +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/tests/test_cli.py +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/tests/test_client.py +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/tests/test_config.py +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/tests/test_dynamodb_routes.py +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/tests/test_endpoint_store.py +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/tests/test_endpoints.py +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/tests/test_iam_routes.py +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/tests/test_lambda_routes.py +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/tests/test_logs_routes.py +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/tests/test_multi_endpoint.py +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/tests/test_readonly_middleware.py +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/tests/test_registries.py +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/tests/test_routes.py +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/tests/test_s3_routes.py +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/tests/test_s3_upload_limit_env.py +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/tests/test_secretsmanager_routes.py +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/tests/test_sqs_routes.py +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/tests/test_tags_routes.py +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/tests/test_websocket.py +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/ui/dist/assets/stepfunctions-graph-B6nvU4OB.js +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/ui/dist/aws-icons/acm.svg +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/ui/dist/aws-icons/apigateway.svg +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/ui/dist/aws-icons/appsync.svg +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/ui/dist/aws-icons/athena.svg +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/ui/dist/aws-icons/cloudformation.svg +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/ui/dist/aws-icons/cloudfront.svg +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/ui/dist/aws-icons/cognito-idp.svg +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/ui/dist/aws-icons/dynamodb.svg +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/ui/dist/aws-icons/ec2.svg +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/ui/dist/aws-icons/ecr.svg +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/ui/dist/aws-icons/ecs.svg +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/ui/dist/aws-icons/elasticache.svg +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/ui/dist/aws-icons/elasticfilesystem.svg +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/ui/dist/aws-icons/elasticloadbalancing.svg +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/ui/dist/aws-icons/elasticmapreduce.svg +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/ui/dist/aws-icons/events.svg +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/ui/dist/aws-icons/firehose.svg +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/ui/dist/aws-icons/glue.svg +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/ui/dist/aws-icons/iam.svg +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/ui/dist/aws-icons/kinesis.svg +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/ui/dist/aws-icons/kms.svg +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/ui/dist/aws-icons/lambda.svg +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/ui/dist/aws-icons/logs.svg +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/ui/dist/aws-icons/monitoring.svg +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/ui/dist/aws-icons/rds.svg +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/ui/dist/aws-icons/route53.svg +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/ui/dist/aws-icons/s3.svg +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/ui/dist/aws-icons/secretsmanager.svg +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/ui/dist/aws-icons/ses.svg +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/ui/dist/aws-icons/sns.svg +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/ui/dist/aws-icons/sqs.svg +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/ui/dist/aws-icons/ssm.svg +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/ui/dist/aws-icons/stepfunctions.svg +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/ui/dist/aws-icons/wafv2.svg +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/ui/dist/favicon.png +0 -0
- {stackport-0.3.4 → stackport-0.3.7}/ui/dist/favicon.svg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: stackport
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.7
|
|
4
4
|
Summary: Universal AWS resource browser for local emulators
|
|
5
5
|
Author: Davi Reis Vieira
|
|
6
6
|
License: MIT
|
|
@@ -29,6 +29,7 @@ Requires-Dist: websockets>=12.0
|
|
|
29
29
|
Requires-Dist: python-multipart>=0.0.26
|
|
30
30
|
Provides-Extra: dev
|
|
31
31
|
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
32
|
+
Requires-Dist: pytest-mock>=3.12.0; extra == "dev"
|
|
32
33
|
Requires-Dist: httpx>=0.27.0; extra == "dev"
|
|
33
34
|
Requires-Dist: moto[dynamodb,iam,lambda,s3,sqs]>=5.0; extra == "dev"
|
|
34
35
|
Dynamic: license-file
|
|
@@ -17,7 +17,7 @@ from botocore.exceptions import (
|
|
|
17
17
|
)
|
|
18
18
|
|
|
19
19
|
from backend.config import LOG_LEVEL, STACKPORT_ALLOW_WRITES, STACKPORT_PORT
|
|
20
|
-
from backend.routes import dynamodb, ec2, endpoints, iam, lambda_svc, logs, resources, s3, secretsmanager, sqs, stats, stepfunctions, tags
|
|
20
|
+
from backend.routes import dynamodb, ec2, endpoints, iam, lambda_svc, logs, resources, rds, s3, secretsmanager, sqs, stats, stepfunctions, tags
|
|
21
21
|
from backend.websocket import probe_loop, websocket_endpoint
|
|
22
22
|
|
|
23
23
|
|
|
@@ -120,12 +120,13 @@ app.include_router(dynamodb.router, prefix="/api/dynamodb")
|
|
|
120
120
|
app.include_router(lambda_svc.router, prefix="/api/lambda", tags=["lambda"])
|
|
121
121
|
app.include_router(sqs.router, prefix="/api/sqs", tags=["sqs"])
|
|
122
122
|
app.include_router(iam.router, prefix="/api/iam", tags=["iam"])
|
|
123
|
-
app.include_router(ec2.router, prefix="/api/ec2", tags=["ec2"])
|
|
123
|
+
app.include_router(ec2.router, prefix="/api/ec2", tags=["ec2", "autoscaling"])
|
|
124
124
|
app.include_router(logs.router, prefix="/api/logs", tags=["logs"])
|
|
125
125
|
app.include_router(secretsmanager.router, prefix="/api/secretsmanager", tags=["secretsmanager"])
|
|
126
126
|
app.include_router(stepfunctions.router, prefix="/api/stepfunctions", tags=["stepfunctions"])
|
|
127
127
|
app.include_router(tags.router, prefix="/api", tags=["tags"])
|
|
128
128
|
app.include_router(resources.router, prefix="/api")
|
|
129
|
+
app.include_router(rds.router, prefix="/api/rds", tags=["rds"])
|
|
129
130
|
|
|
130
131
|
|
|
131
132
|
# WebSocket endpoint for real-time updates
|
|
@@ -38,6 +38,49 @@ def _decode_user_data(encoded: str | None) -> str | None:
|
|
|
38
38
|
except Exception:
|
|
39
39
|
return None
|
|
40
40
|
|
|
41
|
+
def _map_asg_instance(inst: dict) -> dict:
|
|
42
|
+
"""Map an Auto Scaling Group instance to a camelCase shape."""
|
|
43
|
+
return {
|
|
44
|
+
"instanceId": inst.get("InstanceId"),
|
|
45
|
+
"instanceType": inst.get("InstanceType"),
|
|
46
|
+
"lifecycleState": inst.get("LifecycleState"),
|
|
47
|
+
"healthStatus": inst.get("HealthStatus"),
|
|
48
|
+
"availabilityZone": inst.get("AvailabilityZone"),
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@router.get("/asgs")
|
|
53
|
+
def list_autoscaling_groups(ep: EndpointInfo = Depends(get_endpoint_info)) -> dict[str, Any]:
|
|
54
|
+
"""List all Auto Scaling Groups with their instances."""
|
|
55
|
+
try:
|
|
56
|
+
client = get_client("autoscaling", **ep.client_kwargs())
|
|
57
|
+
paginator = client.get_paginator("describe_auto_scaling_groups")
|
|
58
|
+
|
|
59
|
+
all_groups = []
|
|
60
|
+
for page in paginator.paginate():
|
|
61
|
+
for group in page.get("AutoScalingGroups", []):
|
|
62
|
+
created_time = group.get("CreatedTime")
|
|
63
|
+
instances = group.get("Instances", [])
|
|
64
|
+
all_groups.append(
|
|
65
|
+
{
|
|
66
|
+
"autoScalingGroupARN": group["AutoScalingGroupARN"],
|
|
67
|
+
"autoScalingGroupName": group["AutoScalingGroupName"],
|
|
68
|
+
"createdTime": created_time.isoformat() if created_time else None,
|
|
69
|
+
"desiredCapacity": group["DesiredCapacity"],
|
|
70
|
+
"maxSize": group["MaxSize"],
|
|
71
|
+
"minSize": group["MinSize"],
|
|
72
|
+
"availabilityZones": group.get("AvailabilityZones", []),
|
|
73
|
+
"healthCheckGracePeriod": group.get("HealthCheckGracePeriod", 0),
|
|
74
|
+
"instanceCount": len(instances),
|
|
75
|
+
"instances": [_map_asg_instance(i) for i in instances],
|
|
76
|
+
"loadBalancerNames": group.get("LoadBalancerNames", []),
|
|
77
|
+
"tags": group.get("Tags", []),
|
|
78
|
+
}
|
|
79
|
+
)
|
|
80
|
+
return {"auto_scaling_groups": all_groups}
|
|
81
|
+
except Exception as e:
|
|
82
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
83
|
+
|
|
41
84
|
|
|
42
85
|
@router.get("/instances")
|
|
43
86
|
def list_instances(ep: EndpointInfo = Depends(get_endpoint_info)) -> dict[str, Any]:
|
|
@@ -258,6 +301,162 @@ def list_security_groups(ep: EndpointInfo = Depends(get_endpoint_info)) -> dict[
|
|
|
258
301
|
raise HTTPException(status_code=500, detail=str(e))
|
|
259
302
|
|
|
260
303
|
|
|
304
|
+
def _get_rule_id(rule: dict, rule_type: str, idx: int) -> str:
|
|
305
|
+
"""Generate a rule ID based on rule properties."""
|
|
306
|
+
return f"{rule_type}-sgrule-{idx:04d}"
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def _get_ip_version(ip_range: dict | None, ipv6_range: dict | None) -> str:
|
|
310
|
+
"""Determine IP version from rule."""
|
|
311
|
+
if ipv6_range:
|
|
312
|
+
return "IPv6"
|
|
313
|
+
if ip_range:
|
|
314
|
+
cidr = ip_range.get("CidrIp", "")
|
|
315
|
+
return "IPv6" if ":" in cidr else "IPv4"
|
|
316
|
+
return "IPv4"
|
|
317
|
+
|
|
318
|
+
|
|
319
|
+
def _format_port_range(protocol: str, from_port: int | None, to_port: int | None) -> str:
|
|
320
|
+
"""Format port range for display."""
|
|
321
|
+
if protocol == "-1" or protocol == "all":
|
|
322
|
+
return "All"
|
|
323
|
+
if from_port is None and to_port is None:
|
|
324
|
+
return "All"
|
|
325
|
+
if from_port is not None and to_port is not None and from_port != to_port:
|
|
326
|
+
return f"{from_port}-{to_port}"
|
|
327
|
+
if from_port is not None:
|
|
328
|
+
return str(from_port)
|
|
329
|
+
return "All"
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def _expand_rules(ip_permissions: list[dict], rule_type: str) -> list[dict]:
|
|
333
|
+
"""Expand IP permissions into individual rule rows."""
|
|
334
|
+
expanded = []
|
|
335
|
+
rule_idx = 0
|
|
336
|
+
|
|
337
|
+
for perm in ip_permissions:
|
|
338
|
+
protocol = perm.get("IpProtocol", "tcp")
|
|
339
|
+
from_port = perm.get("FromPort")
|
|
340
|
+
to_port = perm.get("ToPort")
|
|
341
|
+
|
|
342
|
+
# Normalize protocol display
|
|
343
|
+
if protocol == "-1":
|
|
344
|
+
protocol = "All"
|
|
345
|
+
|
|
346
|
+
# Expand IPv4 ranges
|
|
347
|
+
for ip_range in perm.get("IpRanges", []):
|
|
348
|
+
expanded.append({
|
|
349
|
+
"ruleId": _get_rule_id(perm, rule_type, rule_idx),
|
|
350
|
+
"ipVersion": "IPv4",
|
|
351
|
+
"type": rule_type.capitalize(),
|
|
352
|
+
"protocol": protocol,
|
|
353
|
+
"portRange": _format_port_range(protocol, from_port, to_port),
|
|
354
|
+
"source": ip_range.get("CidrIp", ""),
|
|
355
|
+
"description": ip_range.get("Description", ""),
|
|
356
|
+
})
|
|
357
|
+
rule_idx += 1
|
|
358
|
+
|
|
359
|
+
# Expand IPv6 ranges
|
|
360
|
+
for ipv6_range in perm.get("Ipv6Ranges", []):
|
|
361
|
+
expanded.append({
|
|
362
|
+
"ruleId": _get_rule_id(perm, rule_type, rule_idx),
|
|
363
|
+
"ipVersion": "IPv6",
|
|
364
|
+
"type": rule_type.capitalize(),
|
|
365
|
+
"protocol": protocol,
|
|
366
|
+
"portRange": _format_port_range(protocol, from_port, to_port),
|
|
367
|
+
"source": ipv6_range.get("CidrIpv6", ""),
|
|
368
|
+
"description": ipv6_range.get("Description", ""),
|
|
369
|
+
})
|
|
370
|
+
rule_idx += 1
|
|
371
|
+
|
|
372
|
+
# Expand prefix list IDs
|
|
373
|
+
for prefix_list in perm.get("PrefixListIds", []):
|
|
374
|
+
expanded.append({
|
|
375
|
+
"ruleId": _get_rule_id(perm, rule_type, rule_idx),
|
|
376
|
+
"ipVersion": "IPv4",
|
|
377
|
+
"type": rule_type.capitalize(),
|
|
378
|
+
"protocol": protocol,
|
|
379
|
+
"portRange": _format_port_range(protocol, from_port, to_port),
|
|
380
|
+
"source": prefix_list.get("PrefixListId", ""),
|
|
381
|
+
"description": "",
|
|
382
|
+
})
|
|
383
|
+
rule_idx += 1
|
|
384
|
+
|
|
385
|
+
# Expand user ID group pairs (security group references)
|
|
386
|
+
for group_pair in perm.get("UserIdGroupPairs", []):
|
|
387
|
+
expanded.append({
|
|
388
|
+
"ruleId": _get_rule_id(perm, rule_type, rule_idx),
|
|
389
|
+
"ipVersion": "IPv4",
|
|
390
|
+
"type": rule_type.capitalize(),
|
|
391
|
+
"protocol": protocol,
|
|
392
|
+
"portRange": _format_port_range(protocol, from_port, to_port),
|
|
393
|
+
"source": group_pair.get("GroupId", "") or group_pair.get("GroupName", ""),
|
|
394
|
+
"description": group_pair.get("Description", ""),
|
|
395
|
+
})
|
|
396
|
+
rule_idx += 1
|
|
397
|
+
|
|
398
|
+
# If no rules found, return empty list (don't add placeholder)
|
|
399
|
+
return expanded
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
@router.get("/security-groups/{group_id}/inbound")
|
|
403
|
+
def get_security_group_inbound_rules(group_id: str, ep: EndpointInfo = Depends(get_endpoint_info)) -> dict[str, Any]:
|
|
404
|
+
"""Get inbound rules for a specific security group with detailed rule information."""
|
|
405
|
+
try:
|
|
406
|
+
client = get_client("ec2", **ep.client_kwargs())
|
|
407
|
+
response = client.describe_security_groups(GroupIds=[group_id])
|
|
408
|
+
|
|
409
|
+
if not response.get("SecurityGroups"):
|
|
410
|
+
raise HTTPException(status_code=404, detail=f"Security group {group_id} not found")
|
|
411
|
+
|
|
412
|
+
sg = response["SecurityGroups"][0]
|
|
413
|
+
inbound_rules = _expand_rules(sg.get("IpPermissions", []), "inbound")
|
|
414
|
+
|
|
415
|
+
return {
|
|
416
|
+
"groupId": group_id,
|
|
417
|
+
"groupName": sg["GroupName"],
|
|
418
|
+
"inboundRules": inbound_rules,
|
|
419
|
+
}
|
|
420
|
+
except HTTPException:
|
|
421
|
+
raise
|
|
422
|
+
except client.exceptions.ClientError as e:
|
|
423
|
+
error_code = e.response["Error"]["Code"]
|
|
424
|
+
if error_code == "InvalidGroupId.NotFound":
|
|
425
|
+
raise HTTPException(status_code=404, detail=f"Security group {group_id} not found")
|
|
426
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
427
|
+
except Exception as e:
|
|
428
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
@router.get("/security-groups/{group_id}/outbound")
|
|
432
|
+
def get_security_group_outbound_rules(group_id: str, ep: EndpointInfo = Depends(get_endpoint_info)) -> dict[str, Any]:
|
|
433
|
+
"""Get outbound rules for a specific security group with detailed rule information."""
|
|
434
|
+
try:
|
|
435
|
+
client = get_client("ec2", **ep.client_kwargs())
|
|
436
|
+
response = client.describe_security_groups(GroupIds=[group_id])
|
|
437
|
+
|
|
438
|
+
if not response.get("SecurityGroups"):
|
|
439
|
+
raise HTTPException(status_code=404, detail=f"Security group {group_id} not found")
|
|
440
|
+
|
|
441
|
+
sg = response["SecurityGroups"][0]
|
|
442
|
+
outbound_rules = _expand_rules(sg.get("IpPermissionsEgress", []), "outbound")
|
|
443
|
+
|
|
444
|
+
return {
|
|
445
|
+
"groupId": group_id,
|
|
446
|
+
"groupName": sg["GroupName"],
|
|
447
|
+
"outboundRules": outbound_rules,
|
|
448
|
+
}
|
|
449
|
+
except HTTPException:
|
|
450
|
+
raise
|
|
451
|
+
except client.exceptions.ClientError as e:
|
|
452
|
+
error_code = e.response["Error"]["Code"]
|
|
453
|
+
if error_code == "InvalidGroupId.NotFound":
|
|
454
|
+
raise HTTPException(status_code=404, detail=f"Security group {group_id} not found")
|
|
455
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
456
|
+
except Exception as e:
|
|
457
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
458
|
+
|
|
459
|
+
|
|
261
460
|
@router.get("/vpcs")
|
|
262
461
|
def list_vpcs(ep: EndpointInfo = Depends(get_endpoint_info)) -> dict[str, Any]:
|
|
263
462
|
"""List all VPCs with their subnets."""
|