Skip to content

A mapping run, step by step

Section 4 · From docs/INVENTORY_INTERNALS.md on GitHub

InventoryMapper(config, tagging_sweep=None).map_inventory():

  1. Copy the configuration. config.model_copy(deep=True); discovery may rewrite the AWS account list, role and regions, and the caller's config object is never mutated.
  2. AWS Organization discovery (aws.organization.enabled). Runs in a worker thread:
    • builds a session in home_region or the primary region with the base credentials;
    • discover_organization() (6);
    • target_accounts() with the OU / exclusion / management / suspended filters, intersected with aws.accounts when that is also set;
    • sets cfg.aws.accounts to the selection and cfg.aws.role_name to organization.role_name, else aws.role_name, else AWSControlTowerExecution;
    • with use_governed_regions and regions: [all], replaces the regions with the Control Tower governed regions. Failure is recorded as coverage (organizations: FAILED) and the run continues with the caller's account only.
  3. Other hierarchies. Azure management groups and policy (azure.map_management_groups), GCP organization / folders / policies (gcp.organization_id and gcp.map_hierarchy).
  4. Collection. MultiAccountCollector(cfg, collector_overrides=…) with the deep collectors:
    • AWS: one task per account × region, limited by concurrency_limit. The account-wide services run only in the primary region (us-east-1 when collected, else the first region), passed as is_primary_region to collectors that declare supports_region_scoping. The caller's own account is collected with the base credentials, because member roles such as AWSControlTowerExecution do not exist in the management account. A failed role assumption records sts_assume_role and skips that account and region.
    • Azure: every configured subscription, or every Enabled subscription (azure.all_subscriptions), one credential for the run. Resource Graph returns all locations, so there is no per-region loop.
    • GCP: one Cloud Asset Inventory listing at organizations/<id> (with project_ids as a filter) when collection_scope allows it, else one per project.
    • Each unit runs collect() then collect_edges() and records coverage. The deep collector options from InventoryConfig are bound through a small subclass (_ConfiguredAWS) that keeps the (session, region, account_id) constructor the orchestrator expects.
  5. Merge discovery assets. topology.to_assets() (with map_structure) and the Azure / GCP hierarchy assets are prepended to the collected assets.
  6. Link (_link):
    1. deduplicate(): one asset per arn, relations and aliases merged, edges re-pointed.
    2. merge_gcp_principals() (GCP only): folds per-project service-account placeholders into the real service accounts.
    3. RelationshipLinker(assets), seed_existing(edges), link(include_generic=link_references).
    4. External account placeholders appended to the assets.
    5. add_account_hierarchy() (with inventory.account_hierarchy).
  7. Result. InventoryResult with the discovery coverage first, the resolved regions, topology.to_dict() and the linker's unresolved references.

CloudGEngine.map_inventory() wraps this and additionally exports the files (and the findings overlay) when output_dir is given. cloudg map adds the console output and --findings.