Lower-level return values¶
21.1 RelationshipLinker¶
linker = RelationshipLinker(assets, materialize_external=True)
linker.seed_existing(edges) # -> None
new_edges = linker.link(include_generic=True) # -> list[NetworkEdge]
linker.external_assets # list[CloudAsset]
linker.unresolved # list[dict], max 2000
linker.resolve(identifier, context=None) # -> str | None (asset id)
link()returns only the edges it created. They are deduplicated on(source_id, target_id, edge_type)against each other and against seeded edges; the generic pass also skips a pair already linked in either direction.external_assetsare theCLOUD_ACCOUNTplaceholders it created; add them to your asset list, the new edges point at them.unresolvedentries are section 11 records.resolve()returns an assetidorNone;context(the referencing asset) scopes ambiguous names.- With
materialize_external=Falsereferences into unmapped accounts are reported as unresolved instead.
21.2 Collectors¶
All inventory collectors implement the BaseCollector interface:
| Method | Returns |
|---|---|
await collect() |
list[CloudAsset] |
await collect_edges() |
list[NetworkEdge], the collector network edges of 5.3 (AWS security group rules and VPC → subnet, Azure NSG rules and VNet → subnet, GCP internet ingress) |
await run() |
tuple[list[CloudAsset], list[NetworkEdge]] |
coverage attribute |
CollectionCoverage with one ServiceCoverage per task (AWS) or step |
The assets carry declared relations but no relationship edges: run them through
RelationshipLinker (or InventoryMapper._link) to get the typed edges. AWSDeepInventoryCollector.collect()
also drops breadth-sweep hits that duplicate a deep-collected asset (by arn and aliases), so
its output has at most one asset per resource.
AzureDeepInventoryCollector.collection_mode is "resource-graph" or "sdk" after collect().
AzureDeepInventoryCollector(credential, subscription_id, graph_client_factory=None,
use_resource_graph=True, *, arm_client_factory=None, dataplane_client_factory=None,
options=None, **options) takes the AzureDeepOptions fields as keyword options (services,
exclude_services, child_resources, max_child_parents, diagnostic_settings,
max_diagnostic_settings, data_plane, kubernetes, kubernetes_timeout,
rest_concurrency); an unknown option raises TypeError. Its coverage attribute is a
CollectionCoverage with one record per task. The factories are credential -> client (ARM
REST: list(path, api_version, **params), get(...), post(...)) and (credential, base_url,
scope) -> client (data planes); tests inject fakes there.
discover_entra(credential, client_factory=None, *, coverage=None, max_objects=10000,
max_expansions=1000) returns the Entra assets (ORGANIZATION, IDENTITY_USER,
IDENTITY_GROUP, APP_REGISTRATION, SERVICE_PRINCIPAL, IAM_POLICY) and records one
coverage entry per Graph listing on coverage.
21.3 Kubernetes¶
| Function | Returns |
|---|---|
eks_token(session, cluster_name, region) |
str, a k8s-aws-v1. bearer token (presigned STS GetCallerIdentity) |
KubernetesReader(endpoint, ca_data_b64, token, timeout=10).get(path, params=None) |
dict, the decoded JSON response |
KubernetesReader.list(path) |
iterator of dict items, following continue tokens, 500 per page, at most 5000 objects |
map_cluster_objects(reader, cluster_arn, region, account_id) |
list[CloudAsset]: K8S_NAMESPACE, K8S_SERVICE_ACCOUNT, K8S_WORKLOAD, K8S_SERVICE, K8S_INGRESS |
collect_eks_workloads(session, cluster, region, account_id, timeout=10) |
list[CloudAsset], the above for one DescribeCluster result |
Kubernetes object assets carry cluster_arn, namespace, kind and, per kind:
| Kind | Metadata | Relations |
|---|---|---|
| Namespace | phase |
cluster CONTAINS / CLUSTER_CONTAINS_SERVICE → namespace |
| ServiceAccount | irsa_role_arn |
namespace contains it; ASSUMES_ROLE / RUNS_ON → IRSA role |
| Deployment, StatefulSet, DaemonSet, CronJob | replicas, ready_replicas, schedule, images, service_account, host_network, privileged_containers, node_selector |
namespace contains it; USES_IMAGE / RUNS_ON → each image's repository; REFERENCES / RUNS_ON → its service account |
| Service | service_type, ports, load_balancer_hostnames, internal |
namespace contains it; LOAD_BALANCER_TARGET / SERVES_TRAFFIC_TO → selected workloads; load balancer ROUTE / LOAD_BALANCED_BY → service (reverse); exposed when LoadBalancer with a hostname |
| Ingress | ingress_class, hosts, backends, load_balancer_hostnames, tls |
namespace contains it; ROUTE / SERVES_TRAFFIC_TO → backend services; load balancer ROUTE / LOAD_BALANCED_BY → ingress; exposed when it has a hostname |
Pod Identity associations are collected by the EKS collector itself, not through the Kubernetes API.
AKS goes through the same mapper:
| Function | Returns |
|---|---|
map_cluster_objects(reader, cluster_arn, region, account_id, provider=CloudProvider.AWS) |
as above; with provider=CloudProvider.AZURE the assets are Azure assets and the AKS rules below apply |
azure_kubernetes.cluster_endpoint(arm, cluster_id) |
(server, ca_b64) from the clusters section of the listClusterUserCredential kubeconfig |
azure_kubernetes.aks_mappable(cluster_row) |
None, or why the cluster is skipped (no Entra integration, stopped) |
azure_kubernetes.collect_aks_workloads(credential, arm, cluster_row, *, timeout=10, reader_factory=None) |
list[CloudAsset] for one AKS cluster, read with an Entra token for 6dae42f8-4368-4678-94ff-3960e28e3630/.default |
On AKS, a ServiceAccount carries workload_identity_client_id and workload_identity_tenant_id
instead of irsa_role_arn, and ASSUMES_ROLE / RUNS_ON → azure-client-id:<client id>, the
user-assigned identity. Service and Ingress load_balancer_hostnames also hold load balancer IPs,
which resolve to public IP assets, and service.beta.kubernetes.io/azure-load-balancer-internal:
"true" sets internal. EKS output is unchanged.
21.4 Catalogs¶
| Function | Module | Returns |
|---|---|---|
load_catalog(name) |
cloudg.inventory.catalogs |
dict, the packaged <name>.yaml merged with $CLOUDG_CATALOG_DIR/<name>.yaml; cached per process |
asset_type_map(mapping, catalog="") |
cloudg.inventory.catalogs |
dict[str, AssetType]; raises ValueError naming the catalog, key and value for an unknown type |
flatten(groups) |
cloudg.inventory.catalogs |
list[str] from a list or a mapping of group → list |
asset_type_from_arn(arn) |
cloudg.inventory.aws_deep_tasks (re-exported by aws_deep) |
AssetType from the aws_arn_types catalog, OTHER when unknown |
select_tasks(names, include=None, exclude=None) |
cloudg.inventory.aws_deep_tasks (re-exported by aws_deep) |
list[str] of task names kept |
21.5 Mapper helpers¶
| Function | Returns |
|---|---|
deduplicate(assets, edges) |
tuple[list[CloudAsset], list[NetworkEdge]]: one asset per arn (the deep-collected copy wins over sweep or placeholder copies; relations and aliases are merged; exposure is OR-ed); edges re-pointed to the kept copy, self-loops dropped, and exact repeats dropped: two edges are repeats when their endpoints, edge_type, port_range, ports, protocol, cidr and direction all match, so parallel security group, NACL and internet-exposure rules survive |
add_account_hierarchy(assets, edges) |
tuple[list[CloudAsset], list[NetworkEdge]]: an account node per (provider, account_id) (reusing organization / subscription / project nodes), and a hierarchy CONTAINS edge to every asset nothing else contains |