Skip to content

Architecture

vpcatlas is a read-only pipeline with four stages. Each stage has one input and one output, so the collectors, the exporters and the dashboard reuse the same data.

collect  ->  graph  ->  layout  ->  export
(boto3)    (networkx)  (geometry)  (drawio, lucid, json, md, html)

Stages

  1. Collect. collectors/ calls the AWS APIs with one collector per service. Every collector implements Collector.collect(session, region) -> list[resource]. Pagination is always used. A service that returns AccessDenied is logged and skipped; it does not stop the run.
  2. Graph. The orchestrator discover(session, regions, concurrency) runs the collectors in parallel per region and builds a NetworkGraph. The graph is a networkx.DiGraph wrapped by the model. Nodes are resources; edges are typed (contains, routes_to, attached_to, peered_with, shared_from). External accounts are nodes too.
  3. Layout. exporters/layout.py computes positions and sizes once. The result is a deterministic grid with the nesting cloud > region > VPC > availability zone > subnet. Both diagram exporters consume the same layout.
  4. Export. exporters/drawio.py writes mxGraph XML with mxgraph.aws4.* shapes. exporters/lucid.py writes a Lucid Standard Import package (a zip with document.json). inventory/ renders the graph to JSON, Markdown and HTML.

Modules

Module Responsibility
model/ Pydantic v2 models for every resource, ExternalAccount and NetworkGraph. Every resource has arn or id, account_id, region, name, tags and a trimmed raw dict.
collectors/ One module per service family: ec2_vpc, ec2_tgw, ec2_peering, vpn_dx, endpoints, elb, network_firewall, route53_resolver, ram, and the service layer: route53, cloudfront, wafv2, acm, shield, targets, ecs, eks, ec2_instances, lambda_fn, apigw, rds, elasticache, efs, s3, dynamodb, account_services (shared helper _dns_targets).
inventory/ Graph to JSON (versioned schema), Markdown and HTML. Sections follow account > region > VPC > subnet > resource, plus "External accounts and cross-account links".
exporters/ layout.py, drawio.py, lucid.py.
dashboard/ FastAPI app, periodic refresh with an in-memory cache, Mangum handler for Lambda.
cli.py Typer commands: discover, inventory, diagram, serve, iam-policy.

Node keys and identity

Node keys are synthetic: {account}/{region}/{type}/{id}. The ARN is an attribute, filled when the API returns it or when it can be composed from the account, region and resource id. The account is the owner of the resource, so a resource shared into your account keeps the owner's account id.

Cross-account detection

A resource belongs to an external account when one of these is true:

  • its owner id differs from the caller account (OwnerId, ARN account segment);
  • a peering connection has AccepterOwnerId or RequesterOwnerId different from the caller account;
  • a Transit Gateway attachment or a Resolver rule is owned by another account;
  • a RAM resource share reports a resourceShareArn owned by another account and makes it visible to you.

Each external account is recorded once, with the id, the way it was seen (for example "TGW attachment via RAM share") and the resource that links it. The account name is taken from organizations when permitted, otherwise the id is used.

Concurrency and failures

  • Regions run concurrently, bounded by concurrency. Collectors inside a region run one after another to keep API throttling low.
  • boto3 retries are left at the SDK defaults with the adaptive retry mode.
  • A failed call is recorded as a warning in the graph metadata and shown in the inventory.