cloudwire 0.2.1__tar.gz → 0.2.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {cloudwire-0.2.1/cloudwire.egg-info → cloudwire-0.2.3}/PKG-INFO +35 -14
- cloudwire-0.2.1/PKG-INFO → cloudwire-0.2.3/README.md +26 -40
- {cloudwire-0.2.1 → cloudwire-0.2.3}/cloudwire/__init__.py +1 -1
- {cloudwire-0.2.1 → cloudwire-0.2.3}/cloudwire/app/graph_store.py +63 -17
- {cloudwire-0.2.1 → cloudwire-0.2.3}/cloudwire/app/main.py +214 -22
- {cloudwire-0.2.1 → cloudwire-0.2.3}/cloudwire/app/models.py +24 -16
- cloudwire-0.2.3/cloudwire/app/scanner.py +703 -0
- cloudwire-0.2.3/cloudwire/app/scanners/__init__.py +4 -0
- cloudwire-0.2.3/cloudwire/app/scanners/_utils.py +35 -0
- cloudwire-0.2.3/cloudwire/app/scanners/apigateway.py +274 -0
- cloudwire-0.2.3/cloudwire/app/scanners/appsync.py +93 -0
- cloudwire-0.2.3/cloudwire/app/scanners/cloudfront.py +84 -0
- cloudwire-0.2.3/cloudwire/app/scanners/cognito.py +74 -0
- cloudwire-0.2.3/cloudwire/app/scanners/dynamodb.py +80 -0
- cloudwire-0.2.3/cloudwire/app/scanners/ec2.py +71 -0
- cloudwire-0.2.3/cloudwire/app/scanners/ecs.py +100 -0
- cloudwire-0.2.3/cloudwire/app/scanners/elasticache.py +40 -0
- cloudwire-0.2.3/cloudwire/app/scanners/eventbridge.py +76 -0
- cloudwire-0.2.3/cloudwire/app/scanners/glue.py +198 -0
- cloudwire-0.2.3/cloudwire/app/scanners/iam.py +28 -0
- cloudwire-0.2.3/cloudwire/app/scanners/kinesis.py +32 -0
- cloudwire-0.2.3/cloudwire/app/scanners/lambda_.py +326 -0
- cloudwire-0.2.3/cloudwire/app/scanners/rds.py +84 -0
- cloudwire-0.2.3/cloudwire/app/scanners/redshift.py +50 -0
- cloudwire-0.2.3/cloudwire/app/scanners/route53.py +140 -0
- cloudwire-0.2.3/cloudwire/app/scanners/s3.py +80 -0
- cloudwire-0.2.3/cloudwire/app/scanners/sns.py +55 -0
- cloudwire-0.2.3/cloudwire/app/scanners/sqs.py +93 -0
- cloudwire-0.2.3/cloudwire/app/scanners/stepfunctions.py +151 -0
- cloudwire-0.2.3/cloudwire/app/scanners/vpc.py +247 -0
- cloudwire-0.2.3/cloudwire/app/services.py +144 -0
- {cloudwire-0.2.1 → cloudwire-0.2.3}/cloudwire/cli.py +38 -3
- cloudwire-0.2.3/cloudwire/static/assets/index-C2eRAgEx.js +40 -0
- cloudwire-0.2.3/cloudwire/static/assets/index-Dkst_1um.css +1 -0
- {cloudwire-0.2.1 → cloudwire-0.2.3}/cloudwire/static/index.html +2 -2
- cloudwire-0.2.1/README.md → cloudwire-0.2.3/cloudwire.egg-info/PKG-INFO +61 -6
- cloudwire-0.2.3/cloudwire.egg-info/SOURCES.txt +44 -0
- cloudwire-0.2.3/cloudwire.egg-info/requires.txt +13 -0
- {cloudwire-0.2.1 → cloudwire-0.2.3}/pyproject.toml +9 -8
- cloudwire-0.2.1/cloudwire/app/scanner.py +0 -2771
- cloudwire-0.2.1/cloudwire/static/assets/index-IhO1P1Kx.js +0 -40
- cloudwire-0.2.1/cloudwire/static/assets/index-ojHsU5ur.css +0 -1
- cloudwire-0.2.1/cloudwire.egg-info/SOURCES.txt +0 -20
- cloudwire-0.2.1/cloudwire.egg-info/requires.txt +0 -13
- {cloudwire-0.2.1 → cloudwire-0.2.3}/cloudwire/app/__init__.py +0 -0
- {cloudwire-0.2.1 → cloudwire-0.2.3}/cloudwire/app/scan_jobs.py +0 -0
- {cloudwire-0.2.1 → cloudwire-0.2.3}/cloudwire/static/favicon.svg +0 -0
- {cloudwire-0.2.1 → cloudwire-0.2.3}/cloudwire.egg-info/dependency_links.txt +0 -0
- {cloudwire-0.2.1 → cloudwire-0.2.3}/cloudwire.egg-info/entry_points.txt +0 -0
- {cloudwire-0.2.1 → cloudwire-0.2.3}/cloudwire.egg-info/top_level.txt +0 -0
- {cloudwire-0.2.1 → cloudwire-0.2.3}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cloudwire
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
4
4
|
Summary: Scan and visualize your AWS infrastructure as an interactive graph
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
Project-URL: Homepage, https://github.com/hisingh_gwre/cloudwire
|
|
@@ -16,6 +16,7 @@ Classifier: Programming Language :: Python :: 3.9
|
|
|
16
16
|
Classifier: Programming Language :: Python :: 3.10
|
|
17
17
|
Classifier: Programming Language :: Python :: 3.11
|
|
18
18
|
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
20
|
Classifier: Topic :: Internet :: WWW/HTTP
|
|
20
21
|
Classifier: Topic :: System :: Systems Administration
|
|
21
22
|
Requires-Python: >=3.9
|
|
@@ -24,20 +25,24 @@ Provides-Extra: dev
|
|
|
24
25
|
Requires-Dist: twine; extra == "dev"
|
|
25
26
|
Requires-Dist: build; extra == "dev"
|
|
26
27
|
Provides-Extra: dependencies
|
|
27
|
-
Requires-Dist: fastapi>=0.
|
|
28
|
-
Requires-Dist: uvicorn
|
|
29
|
-
Requires-Dist: boto3>=1.
|
|
30
|
-
Requires-Dist: botocore>=1.
|
|
31
|
-
Requires-Dist: networkx>=
|
|
32
|
-
Requires-Dist: pydantic>=2.
|
|
33
|
-
Requires-Dist: click>=8.
|
|
28
|
+
Requires-Dist: fastapi>=0.100; extra == "dependencies"
|
|
29
|
+
Requires-Dist: uvicorn>=0.20; extra == "dependencies"
|
|
30
|
+
Requires-Dist: boto3>=1.26; extra == "dependencies"
|
|
31
|
+
Requires-Dist: botocore>=1.29; extra == "dependencies"
|
|
32
|
+
Requires-Dist: networkx>=2.6; extra == "dependencies"
|
|
33
|
+
Requires-Dist: pydantic>=2.0; extra == "dependencies"
|
|
34
|
+
Requires-Dist: click>=8.0; extra == "dependencies"
|
|
34
35
|
|
|
35
|
-
#
|
|
36
|
+
# CloudWire
|
|
36
37
|
|
|
37
38
|
Scan your AWS account and visualize resource dependencies as an interactive graph — directly in your browser, running entirely on your local machine.
|
|
38
39
|
|
|
39
40
|
No data leaves your system. AWS credentials never leave your terminal. The graph is built locally using your existing credential chain (`~/.aws/credentials`, `aws sso login`, `saml2aws`, `aws-vault` — all work out of the box).
|
|
40
41
|
|
|
42
|
+
<p align="center">
|
|
43
|
+
<img src="docs/cloudgraph.svg" alt="CloudWire — AWS infrastructure graph visualization" width="100%">
|
|
44
|
+
</p>
|
|
45
|
+
|
|
41
46
|
---
|
|
42
47
|
|
|
43
48
|
## Install
|
|
@@ -58,7 +63,9 @@ That's it. The browser opens automatically at `http://localhost:8080`.
|
|
|
58
63
|
- Dark hacker-aesthetic graph canvas with animated data flow
|
|
59
64
|
- 24 AWS services with dedicated icons, colors, and role badges
|
|
60
65
|
- Edges represent real relationships — API integrations, event triggers, IAM policy inference, env var references
|
|
61
|
-
-
|
|
66
|
+
- Four layout modes — Circular (default), Flow, Swimlane — switchable from the graph toolbar
|
|
67
|
+
- VPC network topology with CloudMapper-style diagrams — internet anchors, SG rule edges with port labels, AZ grouping, collapsible containers
|
|
68
|
+
- Tag-based scanning — discover and scan resources by AWS tags with searchable multi-select dropdowns
|
|
62
69
|
- Click any node to inspect its attributes, incoming/outgoing edges, and resource-specific tooltip
|
|
63
70
|
- Search, filter by service, highlight upstream/downstream blast radius, find shortest path
|
|
64
71
|
- Permission errors surfaced clearly — see exactly which IAM policies are missing
|
|
@@ -91,6 +98,7 @@ That's it. The browser opens automatically at `http://localhost:8080`.
|
|
|
91
98
|
| AppSync | Dedicated — GraphQL APIs |
|
|
92
99
|
| Secrets Manager | Dedicated |
|
|
93
100
|
| KMS | Dedicated |
|
|
101
|
+
| VPC Network | Dedicated — VPCs, subnets, security groups, IGWs, NAT GWs, route tables; scoped to referenced VPCs |
|
|
94
102
|
| ELB | Discovered via CloudFront, Route 53, ECS edges |
|
|
95
103
|
| Everything else | Generic (tagged resources only) |
|
|
96
104
|
|
|
@@ -108,7 +116,15 @@ cloudwire/ # Python package (the distributable unit)
|
|
|
108
116
|
└── app/ # FastAPI backend
|
|
109
117
|
├── main.py # App factory, API routes (/api/*), static serving
|
|
110
118
|
├── models.py # Pydantic request/response models
|
|
111
|
-
├──
|
|
119
|
+
├── services.py # Canonical service registry — single source of truth
|
|
120
|
+
├── scanner.py # Scan orchestrator, shared helpers, mixin composition
|
|
121
|
+
├── scanners/ # Per-service scanner modules (mixin classes)
|
|
122
|
+
│ ├── _utils.py # Shared constants (ARN pattern, env var conventions)
|
|
123
|
+
│ ├── apigateway.py # REST + HTTP APIs, integrations, authorizers
|
|
124
|
+
│ ├── lambda_.py # Functions, event sources, IAM policy inference
|
|
125
|
+
│ ├── vpc.py # VPCs, subnets, SGs, IGWs, NATs, route tables
|
|
126
|
+
│ ├── glue.py # Jobs, crawlers, triggers
|
|
127
|
+
│ └── ... # 16 more service scanners (one per AWS service)
|
|
112
128
|
├── scan_jobs.py # Async job store with progress tracking
|
|
113
129
|
└── graph_store.py # networkx graph with thread-safe mutations
|
|
114
130
|
|
|
@@ -117,12 +133,17 @@ frontend/ # React + Vite source (compiled into cloudwire/s
|
|
|
117
133
|
│ ├── pages/CloudWirePage.jsx # Main page — orchestrates all state
|
|
118
134
|
│ ├── components/
|
|
119
135
|
│ │ ├── graph/ # GraphCanvas, GraphNode, GraphEdge, Minimap, Legend
|
|
120
|
-
│ │
|
|
136
|
+
│ │ ├── layout/ # TopBar, ServiceSidebar, InspectorPanel, TagFilterBar
|
|
137
|
+
│ │ └── ErrorBoundary.jsx # React error boundary for graceful pane crashes
|
|
121
138
|
│ ├── hooks/
|
|
122
139
|
│ │ ├── useScanPolling.js # Scan lifecycle, polling, graph data state
|
|
140
|
+
│ │ ├── useTagDiscovery.js # Tag-based resource discovery
|
|
141
|
+
│ │ ├── useGraphPipeline.js # Graph filtering, clustering, layout pipeline
|
|
142
|
+
│ │ ├── usePathFinder.js # Shortest-path mode state management
|
|
143
|
+
│ │ ├── useClickOutside.js # Shared click-outside hook
|
|
123
144
|
│ │ └── useGraphViewport.js # Pan/zoom viewport state
|
|
124
145
|
│ ├── lib/
|
|
125
|
-
│ │ ├── graphTransforms.js # Layout algorithms
|
|
146
|
+
│ │ ├── graphTransforms.js # Layout algorithms, annotations, container collapse
|
|
126
147
|
│ │ ├── serviceVisuals.jsx # Service icon + color map
|
|
127
148
|
│ │ └── awsRegions.js # AWS region list
|
|
128
149
|
│ └── styles/graph.css # All UI styles
|
|
@@ -172,7 +193,7 @@ This starts the FastAPI backend on `:8000` (with `--reload`) and the Vite dev se
|
|
|
172
193
|
|
|
173
194
|
| Area | Where to edit |
|
|
174
195
|
|------|--------------|
|
|
175
|
-
| Add a new AWS service scanner | `cloudwire/app/
|
|
196
|
+
| Add a new AWS service scanner | `cloudwire/app/scanners/` → create a mixin class, import it in `scanner.py`, add to the class bases and `service_scanners` dict |
|
|
176
197
|
| Change graph layout | `frontend/src/lib/graphTransforms.js` |
|
|
177
198
|
| Add a new UI component | `frontend/src/components/` |
|
|
178
199
|
| Change API routes | `cloudwire/app/main.py` — all routes are under the `/api` prefix |
|
|
@@ -1,43 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
Name: cloudwire
|
|
3
|
-
Version: 0.2.1
|
|
4
|
-
Summary: Scan and visualize your AWS infrastructure as an interactive graph
|
|
5
|
-
License-Expression: MIT
|
|
6
|
-
Project-URL: Homepage, https://github.com/hisingh_gwre/cloudwire
|
|
7
|
-
Project-URL: Issues, https://github.com/hisingh_gwre/cloudwire/issues
|
|
8
|
-
Keywords: aws,cloud,visualization,graph,infrastructure
|
|
9
|
-
Classifier: Development Status :: 3 - Alpha
|
|
10
|
-
Classifier: Environment :: Console
|
|
11
|
-
Classifier: Environment :: Web Environment
|
|
12
|
-
Classifier: Intended Audience :: Developers
|
|
13
|
-
Classifier: Intended Audience :: System Administrators
|
|
14
|
-
Classifier: Programming Language :: Python :: 3
|
|
15
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
-
Classifier: Topic :: Internet :: WWW/HTTP
|
|
20
|
-
Classifier: Topic :: System :: Systems Administration
|
|
21
|
-
Requires-Python: >=3.9
|
|
22
|
-
Description-Content-Type: text/markdown
|
|
23
|
-
Provides-Extra: dev
|
|
24
|
-
Requires-Dist: twine; extra == "dev"
|
|
25
|
-
Requires-Dist: build; extra == "dev"
|
|
26
|
-
Provides-Extra: dependencies
|
|
27
|
-
Requires-Dist: fastapi>=0.115; extra == "dependencies"
|
|
28
|
-
Requires-Dist: uvicorn[standard]>=0.34; extra == "dependencies"
|
|
29
|
-
Requires-Dist: boto3>=1.37; extra == "dependencies"
|
|
30
|
-
Requires-Dist: botocore>=1.37; extra == "dependencies"
|
|
31
|
-
Requires-Dist: networkx>=3.4; extra == "dependencies"
|
|
32
|
-
Requires-Dist: pydantic>=2.11; extra == "dependencies"
|
|
33
|
-
Requires-Dist: click>=8.1; extra == "dependencies"
|
|
34
|
-
|
|
35
|
-
# Cloudwire
|
|
1
|
+
# CloudWire
|
|
36
2
|
|
|
37
3
|
Scan your AWS account and visualize resource dependencies as an interactive graph — directly in your browser, running entirely on your local machine.
|
|
38
4
|
|
|
39
5
|
No data leaves your system. AWS credentials never leave your terminal. The graph is built locally using your existing credential chain (`~/.aws/credentials`, `aws sso login`, `saml2aws`, `aws-vault` — all work out of the box).
|
|
40
6
|
|
|
7
|
+
<p align="center">
|
|
8
|
+
<img src="docs/cloudgraph.svg" alt="CloudWire — AWS infrastructure graph visualization" width="100%">
|
|
9
|
+
</p>
|
|
10
|
+
|
|
41
11
|
---
|
|
42
12
|
|
|
43
13
|
## Install
|
|
@@ -58,7 +28,9 @@ That's it. The browser opens automatically at `http://localhost:8080`.
|
|
|
58
28
|
- Dark hacker-aesthetic graph canvas with animated data flow
|
|
59
29
|
- 24 AWS services with dedicated icons, colors, and role badges
|
|
60
30
|
- Edges represent real relationships — API integrations, event triggers, IAM policy inference, env var references
|
|
61
|
-
-
|
|
31
|
+
- Four layout modes — Circular (default), Flow, Swimlane — switchable from the graph toolbar
|
|
32
|
+
- VPC network topology with CloudMapper-style diagrams — internet anchors, SG rule edges with port labels, AZ grouping, collapsible containers
|
|
33
|
+
- Tag-based scanning — discover and scan resources by AWS tags with searchable multi-select dropdowns
|
|
62
34
|
- Click any node to inspect its attributes, incoming/outgoing edges, and resource-specific tooltip
|
|
63
35
|
- Search, filter by service, highlight upstream/downstream blast radius, find shortest path
|
|
64
36
|
- Permission errors surfaced clearly — see exactly which IAM policies are missing
|
|
@@ -91,6 +63,7 @@ That's it. The browser opens automatically at `http://localhost:8080`.
|
|
|
91
63
|
| AppSync | Dedicated — GraphQL APIs |
|
|
92
64
|
| Secrets Manager | Dedicated |
|
|
93
65
|
| KMS | Dedicated |
|
|
66
|
+
| VPC Network | Dedicated — VPCs, subnets, security groups, IGWs, NAT GWs, route tables; scoped to referenced VPCs |
|
|
94
67
|
| ELB | Discovered via CloudFront, Route 53, ECS edges |
|
|
95
68
|
| Everything else | Generic (tagged resources only) |
|
|
96
69
|
|
|
@@ -108,7 +81,15 @@ cloudwire/ # Python package (the distributable unit)
|
|
|
108
81
|
└── app/ # FastAPI backend
|
|
109
82
|
├── main.py # App factory, API routes (/api/*), static serving
|
|
110
83
|
├── models.py # Pydantic request/response models
|
|
111
|
-
├──
|
|
84
|
+
├── services.py # Canonical service registry — single source of truth
|
|
85
|
+
├── scanner.py # Scan orchestrator, shared helpers, mixin composition
|
|
86
|
+
├── scanners/ # Per-service scanner modules (mixin classes)
|
|
87
|
+
│ ├── _utils.py # Shared constants (ARN pattern, env var conventions)
|
|
88
|
+
│ ├── apigateway.py # REST + HTTP APIs, integrations, authorizers
|
|
89
|
+
│ ├── lambda_.py # Functions, event sources, IAM policy inference
|
|
90
|
+
│ ├── vpc.py # VPCs, subnets, SGs, IGWs, NATs, route tables
|
|
91
|
+
│ ├── glue.py # Jobs, crawlers, triggers
|
|
92
|
+
│ └── ... # 16 more service scanners (one per AWS service)
|
|
112
93
|
├── scan_jobs.py # Async job store with progress tracking
|
|
113
94
|
└── graph_store.py # networkx graph with thread-safe mutations
|
|
114
95
|
|
|
@@ -117,12 +98,17 @@ frontend/ # React + Vite source (compiled into cloudwire/s
|
|
|
117
98
|
│ ├── pages/CloudWirePage.jsx # Main page — orchestrates all state
|
|
118
99
|
│ ├── components/
|
|
119
100
|
│ │ ├── graph/ # GraphCanvas, GraphNode, GraphEdge, Minimap, Legend
|
|
120
|
-
│ │
|
|
101
|
+
│ │ ├── layout/ # TopBar, ServiceSidebar, InspectorPanel, TagFilterBar
|
|
102
|
+
│ │ └── ErrorBoundary.jsx # React error boundary for graceful pane crashes
|
|
121
103
|
│ ├── hooks/
|
|
122
104
|
│ │ ├── useScanPolling.js # Scan lifecycle, polling, graph data state
|
|
105
|
+
│ │ ├── useTagDiscovery.js # Tag-based resource discovery
|
|
106
|
+
│ │ ├── useGraphPipeline.js # Graph filtering, clustering, layout pipeline
|
|
107
|
+
│ │ ├── usePathFinder.js # Shortest-path mode state management
|
|
108
|
+
│ │ ├── useClickOutside.js # Shared click-outside hook
|
|
123
109
|
│ │ └── useGraphViewport.js # Pan/zoom viewport state
|
|
124
110
|
│ ├── lib/
|
|
125
|
-
│ │ ├── graphTransforms.js # Layout algorithms
|
|
111
|
+
│ │ ├── graphTransforms.js # Layout algorithms, annotations, container collapse
|
|
126
112
|
│ │ ├── serviceVisuals.jsx # Service icon + color map
|
|
127
113
|
│ │ └── awsRegions.js # AWS region list
|
|
128
114
|
│ └── styles/graph.css # All UI styles
|
|
@@ -172,7 +158,7 @@ This starts the FastAPI backend on `:8000` (with `--reload`) and the Vite dev se
|
|
|
172
158
|
|
|
173
159
|
| Area | Where to edit |
|
|
174
160
|
|------|--------------|
|
|
175
|
-
| Add a new AWS service scanner | `cloudwire/app/
|
|
161
|
+
| Add a new AWS service scanner | `cloudwire/app/scanners/` → create a mixin class, import it in `scanner.py`, add to the class bases and `service_scanners` dict |
|
|
176
162
|
| Change graph layout | `frontend/src/lib/graphTransforms.js` |
|
|
177
163
|
| Add a new UI component | `frontend/src/components/` |
|
|
178
164
|
| Change API routes | `cloudwire/app/main.py` — all routes are under the `/api` prefix |
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import logging
|
|
3
4
|
from datetime import datetime, timezone
|
|
4
5
|
from threading import Lock
|
|
5
|
-
from typing import Any, Dict, List, Set
|
|
6
|
+
from typing import Any, Dict, List, Set, Tuple
|
|
6
7
|
|
|
7
8
|
import networkx as nx
|
|
8
9
|
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
9
12
|
|
|
10
13
|
class GraphStore:
|
|
11
14
|
def __init__(self) -> None:
|
|
@@ -71,6 +74,27 @@ class GraphStore:
|
|
|
71
74
|
metadata["edge_count"] = len(edges)
|
|
72
75
|
return {"nodes": nodes, "edges": edges, "metadata": metadata}
|
|
73
76
|
|
|
77
|
+
def iter_nodes_by_service(self, service: str) -> List[Tuple[str, Dict[str, Any]]]:
|
|
78
|
+
"""Return a snapshot of (node_id, attrs_copy) pairs for a given service."""
|
|
79
|
+
with self._lock:
|
|
80
|
+
return [
|
|
81
|
+
(nid, dict(attrs))
|
|
82
|
+
for nid, attrs in self.graph.nodes(data=True)
|
|
83
|
+
if attrs.get("service") == service
|
|
84
|
+
]
|
|
85
|
+
|
|
86
|
+
def snapshot_graph(self) -> "nx.DiGraph":
|
|
87
|
+
"""Return a shallow copy of the graph for read-only traversal."""
|
|
88
|
+
with self._lock:
|
|
89
|
+
return self.graph.copy()
|
|
90
|
+
|
|
91
|
+
def batch_update_nodes(self, updates: List[Tuple[str, Dict[str, Any]]]) -> None:
|
|
92
|
+
"""Apply attribute updates to multiple nodes atomically."""
|
|
93
|
+
with self._lock:
|
|
94
|
+
for node_id, attrs in updates:
|
|
95
|
+
if self.graph.has_node(node_id):
|
|
96
|
+
self.graph.nodes[node_id].update(attrs)
|
|
97
|
+
|
|
74
98
|
def _node_matches_arns(self, node_id: str, attrs: Dict[str, Any], allowed_arns: Set[str]) -> bool:
|
|
75
99
|
"""Check if a node matches any of the allowed ARNs.
|
|
76
100
|
|
|
@@ -91,39 +115,46 @@ class GraphStore:
|
|
|
91
115
|
return True
|
|
92
116
|
return False
|
|
93
117
|
|
|
94
|
-
def filter_by_arns(self, allowed_arns: Set[str]) -> int:
|
|
118
|
+
def filter_by_arns(self, allowed_arns: Set[str]) -> Dict[str, int]:
|
|
95
119
|
"""Remove nodes that don't match the allowed ARNs, preserving neighbors.
|
|
96
120
|
|
|
97
121
|
Keeps:
|
|
98
122
|
- Nodes whose ARN matches the allowed set (the "seed" nodes)
|
|
99
|
-
- Direct neighbors of seed nodes (1-hop)
|
|
100
|
-
- VPC infrastructure ancestors of kept nodes
|
|
101
|
-
|
|
102
|
-
- Nodes without any ARN-like attribute (synthetic/connector nodes)
|
|
103
|
-
Returns the number of nodes removed.
|
|
123
|
+
- Direct neighbors of seed nodes (1-hop), marked ``kept_by_proximity=True``
|
|
124
|
+
- VPC infrastructure ancestors of kept nodes
|
|
125
|
+
Returns dict with ``seeds``, ``neighbors``, ``removed``, ``total`` counts.
|
|
104
126
|
"""
|
|
105
127
|
with self._lock:
|
|
128
|
+
total = self.graph.number_of_nodes()
|
|
129
|
+
logger.debug(
|
|
130
|
+
"filter_by_arns: %d graph nodes, %d allowed ARNs",
|
|
131
|
+
total, len(allowed_arns),
|
|
132
|
+
)
|
|
106
133
|
# Phase 1: identify seed nodes (directly matched by ARN)
|
|
107
134
|
seed_ids: Set[str] = set()
|
|
108
|
-
no_arn_ids: Set[str] = set()
|
|
109
135
|
for node_id, attrs in self.graph.nodes(data=True):
|
|
110
136
|
if self._node_matches_arns(node_id, attrs, allowed_arns):
|
|
111
137
|
seed_ids.add(node_id)
|
|
112
|
-
|
|
113
|
-
|
|
138
|
+
# Note: nodes without arn/real_arn that have NO edges are
|
|
139
|
+
# phantom inferred nodes — they will be removed unless they
|
|
140
|
+
# are neighbors of seeds. This avoids polluting the tag graph
|
|
141
|
+
# with Lambda env-var inferred phantom nodes.
|
|
114
142
|
|
|
115
143
|
# Phase 2: expand to direct neighbors of seeds (1-hop)
|
|
116
|
-
|
|
144
|
+
# Mark them so the frontend can visually distinguish proximity nodes
|
|
145
|
+
neighbor_ids: Set[str] = set()
|
|
117
146
|
for seed_id in seed_ids:
|
|
118
147
|
for neighbor in self.graph.predecessors(seed_id):
|
|
119
|
-
|
|
148
|
+
if neighbor not in seed_ids:
|
|
149
|
+
neighbor_ids.add(neighbor)
|
|
120
150
|
for neighbor in self.graph.successors(seed_id):
|
|
121
|
-
|
|
151
|
+
if neighbor not in seed_ids:
|
|
152
|
+
neighbor_ids.add(neighbor)
|
|
153
|
+
|
|
154
|
+
keep_ids = seed_ids | neighbor_ids
|
|
122
155
|
|
|
123
156
|
# Phase 3: walk VPC infrastructure ancestors so topology context
|
|
124
157
|
# (VPC → subnet → resource, IGW → VPC, RTB → subnet) stays intact.
|
|
125
|
-
# For any kept VPC infra node, also keep its predecessors/successors
|
|
126
|
-
# that are VPC infra, up the containment chain.
|
|
127
158
|
vpc_frontier = [
|
|
128
159
|
nid for nid in keep_ids
|
|
129
160
|
if self.graph.nodes[nid].get("service") == "vpc"
|
|
@@ -136,6 +167,7 @@ class GraphStore:
|
|
|
136
167
|
attrs = self.graph.nodes[neighbor]
|
|
137
168
|
if attrs.get("service") == "vpc":
|
|
138
169
|
keep_ids.add(neighbor)
|
|
170
|
+
neighbor_ids.add(neighbor)
|
|
139
171
|
visited.add(neighbor)
|
|
140
172
|
vpc_frontier.append(neighbor)
|
|
141
173
|
for neighbor in self.graph.successors(nid):
|
|
@@ -143,17 +175,31 @@ class GraphStore:
|
|
|
143
175
|
attrs = self.graph.nodes[neighbor]
|
|
144
176
|
if attrs.get("service") == "vpc":
|
|
145
177
|
keep_ids.add(neighbor)
|
|
178
|
+
neighbor_ids.add(neighbor)
|
|
146
179
|
visited.add(neighbor)
|
|
147
180
|
vpc_frontier.append(neighbor)
|
|
148
181
|
|
|
149
|
-
# Phase 4: remove everything else
|
|
182
|
+
# Phase 4: mark proximity nodes and remove everything else
|
|
183
|
+
for nid in neighbor_ids:
|
|
184
|
+
if self.graph.has_node(nid):
|
|
185
|
+
self.graph.nodes[nid]["kept_by_proximity"] = True
|
|
186
|
+
|
|
150
187
|
nodes_to_remove = [
|
|
151
188
|
node_id for node_id in self.graph.nodes()
|
|
152
189
|
if node_id not in keep_ids
|
|
153
190
|
]
|
|
154
191
|
for node_id in nodes_to_remove:
|
|
155
192
|
self.graph.remove_node(node_id)
|
|
156
|
-
|
|
193
|
+
logger.debug(
|
|
194
|
+
"filter_by_arns: seeds=%d, neighbors=%d, kept=%d, removed=%d",
|
|
195
|
+
len(seed_ids), len(neighbor_ids), len(keep_ids), len(nodes_to_remove),
|
|
196
|
+
)
|
|
197
|
+
return {
|
|
198
|
+
"seeds": len(seed_ids),
|
|
199
|
+
"neighbors": len(neighbor_ids),
|
|
200
|
+
"removed": len(nodes_to_remove),
|
|
201
|
+
"total": total,
|
|
202
|
+
}
|
|
157
203
|
|
|
158
204
|
def get_resource_payload(self, resource_id: str) -> Dict[str, Any]:
|
|
159
205
|
with self._lock:
|