DEV Community

Cover image for AWS Global View: The Ultimate Multi-Region Resource Visibility Guide (CLI + Terraform + CDK + Python)
Manish Kumar
Manish Kumar

Posted on

AWS Global View: The Ultimate Multi-Region Resource Visibility Guide (CLI + Terraform + CDK + Python)

Introduction

If you manage AWS infrastructure across multiple regions — which every production-grade setup does — you've felt the pain of jumping between region tabs in the console just to check how many EC2 instances are running globally, or hunting down an orphaned EIP you forgot about in ap-southeast-1. AWS Global View solves exactly that problem.

AWS Global View is a read-only, single-pane-of-glass console built directly into the EC2/VPC console at us-east-1 that gives you a unified summary of EC2 and VPC resources across every AWS region your account is enabled in. It requires zero setup, zero cost, and zero configuration. Yet, most AWS engineers don't know it exists — and even fewer know how to replicate and extend its capabilities programmatically using CLI, Terraform, CDK, and boto3.

In this blog, we cover every angle: from the console deep-dive to full IaC automation and Python-based global inventory scripts you can drop straight into production.

What is AWS Global View?

AWS Global View is a read-only multi-region resource explorer embedded inside the AWS EC2 console. It was designed to give you a bird's-eye view of your EC2 and VPC fleet without switching regions. It does NOT allow you to create, modify, or delete resources — it is purely an observation tool.

The console is accessible at:

https://<account-id>.us-east-1.console.aws.amazon.com/awsglobalview/home?region=us-east-1#RegionExplorer
Enter fullscreen mode Exit fullscreen mode

Pro Tip: AWS Global View only works in us-east-1 as the home region. You cannot access it from other regional console endpoints. Also, Firefox private/incognito windows are not supported.

The service is tightly integrated with AWS Resource Explorer v2, which provides the underlying index and search engine powering global search features .

Supported Resources

AWS Global View currently supports the following resources across all enabled regions:

Category Resources
Compute EC2 Instances, Auto Scaling Groups, Capacity Reservations, Capacity Blocks
Networking VPCs, Subnets, Internet Gateways, Egress-only IGWs, NAT Gateways, Route Tables, Network ACLs, Network Interfaces, Elastic IPs, Security Groups
Connectivity VPC Endpoints, VPC Peering Connections, Endpoint Services, Managed Prefix Lists
Storage EBS Volumes, S3 Buckets
Database RDS DB Instances, DB Clusters
Infrastructure DHCP Option Sets, Outposts, Availability Zones

Console Deep Dive

The Global View console has four key sections:

Region Explorer

The landing page. It shows a Summary section at the top — total count of each resource type across all enabled regions, with clickable links. For example, if it says "29 Instances in 10 Regions", clicking that link shows all 29 with their regions listed. Below the summary is a per-region breakdown table — every region as a row, every resource type as a column, with counts. Click any cell to drill into that specific resource type in that region.

Global Search

The search tab lets you filter by Region, Resource Type, and Tags simultaneously. This is the closest thing AWS has to a native cross-region asset inventory. You can click any resource ID to jump directly into its native console (e.g., clicking an instance ID opens it in the EC2 console for that specific region).

Regions and Zones

This tab lists all AWS Regions, Availability Zones, Local Zones, and Wavelength Zones for your account. From here you can enable/disable regions and opt-in to Local Zones — critical for compliance use cases where you need to restrict infrastructure to specific geographies.

Settings

Customize the console to hide resource types and regions you don't use. If your organization only uses us-east-1, ap-south-1, and eu-west-1, hide everything else to reduce noise.

Required IAM Permissions

A user needs read-level ec2:Describe* and rds:Describe* permissions at minimum to access Global View. Here is the least-privilege policy:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "GlobalViewReadOnly",
      "Effect": "Allow",
      "Action": [
        "ec2:DescribeRegions",
        "ec2:DescribeInstances",
        "ec2:DescribeVpcs",
        "ec2:DescribeSubnets",
        "ec2:DescribeSecurityGroups",
        "ec2:DescribeVolumes",
        "ec2:DescribeNatGateways",
        "ec2:DescribeInternetGateways",
        "ec2:DescribeRouteTables",
        "ec2:DescribeNetworkAcls",
        "ec2:DescribeNetworkInterfaces",
        "ec2:DescribeAddresses",
        "ec2:DescribeAvailabilityZones",
        "ec2:DescribeVpcPeeringConnections",
        "ec2:DescribeVpcEndpoints",
        "ec2:DescribeManagedPrefixLists",
        "ec2:DescribeAutoScalingGroups",
        "rds:DescribeDBInstances",
        "rds:DescribeDBClusters",
        "s3:ListAllMyBuckets",
        "account:GetRegionOptStatus",
        "account:ListRegions"
      ],
      "Resource": "*"
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Security Best Practice: Attach this policy to an IAM Role, not an IAM User. If you're using this from a Lambda function or GitHub Actions CI/CD runner, use role assumption with STS — never hardcode access keys.

Section 1: AWS CLI — Replicate Global View in the Terminal

AWS Global View has no dedicated CLI command. Instead, you replicate its behavior using ec2 and account CLI subcommands. All examples below assume you have the AWS CLI v2 configured with appropriate credentials.

1.1 — List All Enabled Regions

aws ec2 describe-regions \
  --filters "Name=opt-in-status,Values=opt-in-not-required,opted-in" \
  --query "Regions[*].RegionName" \
  --output table
Enter fullscreen mode Exit fullscreen mode

This gives you the exact list Global View uses to build its region table.

1.2 — Count EC2 Instances Per Region

echo "Region | Instance Count" && echo "-------|---------------"
for region in $(aws ec2 describe-regions \
  --filters "Name=opt-in-status,Values=opt-in-not-required,opted-in" \
  --query "Regions[*].RegionName" --output text); do
  count=$(aws ec2 describe-instances \
    --region "$region" \
    --query "length(Reservations[*].Instances[])" \
    --output text 2>/dev/null || echo 0)
  echo "$region | $count"
done
Enter fullscreen mode Exit fullscreen mode

1.3 — List All VPCs Globally with CIDR Blocks

for region in $(aws ec2 describe-regions \
  --query "Regions[*].RegionName" --output text); do
  echo "=== Region: $region ==="
  aws ec2 describe-vpcs \
    --region "$region" \
    --query "Vpcs[*].{VpcId:VpcId,CIDR:CidrBlock,Default:IsDefault,State:State}" \
    --output table 2>/dev/null
done
Enter fullscreen mode Exit fullscreen mode

1.4 — Find All Unattached EBS Volumes (FinOps Use Case)

Orphaned EBS volumes are one of the biggest sources of wasted AWS spend. This script finds them globally:

echo "Hunting orphaned volumes across all regions..."
for region in $(aws ec2 describe-regions \
  --query "Regions[*].RegionName" --output text); do
  result=$(aws ec2 describe-volumes \
    --region "$region" \
    --filters "Name=status,Values=available" \
    --query "Volumes[*].{Region:'$region',VolumeId:VolumeId,Size:Size,Type:VolumeType}" \
    --output table 2>/dev/null)
  if [[ -n "$result" && "$result" != *"None"* ]]; then
    echo "=== $region ==="
    echo "$result"
  fi
done
Enter fullscreen mode Exit fullscreen mode

1.5 — Find Open Security Groups (0.0.0.0/0 on Port 22 or 3389)

This is a global security audit you'd normally do manually per-region:

for region in $(aws ec2 describe-regions \
  --query "Regions[*].RegionName" --output text); do
  aws ec2 describe-security-groups \
    --region "$region" \
    --filters "Name=ip-permission.from-port,Values=22" \
             "Name=ip-permission.cidr,Values=0.0.0.0/0" \
    --query "SecurityGroups[*].{Region:'$region',GroupId:GroupId,GroupName:GroupName,VpcId:VpcId}" \
    --output table 2>/dev/null
done
Enter fullscreen mode Exit fullscreen mode

1.6 — Enable/Disable a Region via CLI

# Check region opt-in status
aws account get-region-opt-status \
  --region-name ap-east-1 \
  --region us-east-1

# Enable a region (opt-in regions like ap-east-1, me-south-1)
aws account enable-region \
  --region-name ap-east-1 \
  --region us-east-1

# Disable a region
aws account disable-region \
  --region-name ap-east-1 \
  --region us-east-1
Enter fullscreen mode Exit fullscreen mode

Important: Region enable/disable operations require the account:EnableRegion and account:DisableRegion IAM permissions, and must be called against the us-east-1 endpoint regardless of target region.

Section 2: Resource Explorer v2 CLI — The API Behind Global Search

AWS Resource Explorer v2 is the underlying engine that powers the Global Search tab in Global View . Unlike Global View (which is console-only), Resource Explorer has a full API and CLI.

2.1 — Enable Resource Explorer with an Aggregator Index

The aggregator index is the "master" that collects data from local indexes in all other regions :

# Step 1: Create local indexes in each region you want to cover
for region in ap-south-1 eu-west-1 us-west-2 ap-southeast-1; do
  echo "Creating local index in $region..."
  aws resource-explorer-2 create-index \
    --type LOCAL \
    --region "$region"
done

# Step 2: Create the aggregator index in us-east-1
aws resource-explorer-2 create-index \
  --type AGGREGATOR \
  --region us-east-1
Enter fullscreen mode Exit fullscreen mode

If you already have a local index in us-east-1, upgrade it to AGGREGATOR:

# Get the index ARN first
aws resource-explorer-2 get-index --region us-east-1

# Then upgrade it
aws resource-explorer-2 update-index-type \
  --arn "arn:aws:resource-explorer-2:us-east-1:123456789012:index/YOUR-INDEX-ID" \
  --type AGGREGATOR \
  --region us-east-1
Enter fullscreen mode Exit fullscreen mode

2.2 — Create Search Views

Views are saved search filters — like a named query for cross-region searches :

# Create a view for all resources (shows all tags)
aws resource-explorer-2 create-view \
  --view-name "All-Resources-Global" \
  --included-properties Name=tags \
  --region us-east-1

# Create an EC2-only view
aws resource-explorer-2 create-view \
  --view-name "EC2-Only-Global" \
  --included-properties Name=tags \
  --filters FilterString="service:ec2" \
  --region us-east-1

# Set a view as default for the region
aws resource-explorer-2 associate-default-view \
  --view-arn "arn:aws:resource-explorer-2:us-east-1:123456789012:view/All-Resources-Global/YOUR-VIEW-ID"
Enter fullscreen mode Exit fullscreen mode

2.3 — Search Resources Globally via CLI

# Search all EC2 instances globally
aws resource-explorer-2 search \
  --query-string "resourcetype:ec2:instance" \
  --region us-east-1

# Search by tag (e.g., Environment=Production)
aws resource-explorer-2 search \
  --query-string "tag.Environment=Production" \
  --region us-east-1

# Search for all untagged resources
aws resource-explorer-2 search \
  --query-string "-tag.Environment" \
  --region us-east-1

# Search for RDS instances across all regions
aws resource-explorer-2 search \
  --query-string "service:rds resourcetype:rds:db" \
  --region us-east-1

# Use a specific view for EC2-scoped search
aws resource-explorer-2 search \
  --query-string "*" \
  --view-arn "arn:aws:resource-explorer-2:us-east-1:123456789012:view/EC2-Only-Global/YOUR-VIEW-ID" \
  --region us-east-1
Enter fullscreen mode Exit fullscreen mode

2.4 — List All Resource Types Supported by Resource Explorer

# Get all supported resource types (paginated)
aws resource-explorer-2 list-supported-resource-types \
  --max-items 50 \
  --region us-east-1

# Get next page using the NextToken
aws resource-explorer-2 list-supported-resource-types \
  --max-items 50 \
  --starting-token "NEXT_TOKEN_HERE" \
  --region us-east-1
Enter fullscreen mode Exit fullscreen mode

Section 3: Infrastructure as Code — Terraform

3.1 — Multi-Region Provider Setup

Always define providers as aliases in Terraform when working across multiple regions. Here's a production-grade providers.tf:

# providers.tf
terraform {
  required_version = ">= 1.6.0"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.40"
    }
  }
}

# Default provider — us-east-1 (where Global View lives)
provider "aws" {
  region = "us-east-1"
}

provider "aws" {
  alias  = "us_east_1"
  region = "us-east-1"
}

provider "aws" {
  alias  = "us_west_2"
  region = "us-west-2"
}

provider "aws" {
  alias  = "ap_south_1"
  region = "ap-south-1"
}

provider "aws" {
  alias  = "eu_west_1"
  region = "eu-west-1"
}
Enter fullscreen mode Exit fullscreen mode

3.2 — IAM Policy and Role for Global View Access

# iam.tf
resource "aws_iam_policy" "global_view_readonly" {
  name        = "GlobalViewReadOnlyPolicy"
  description = "Least-privilege read-only policy for AWS Global View and Resource Explorer"
  path        = "/"

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Sid    = "EC2GlobalViewAccess"
        Effect = "Allow"
        Action = [
          "ec2:DescribeRegions",
          "ec2:DescribeInstances",
          "ec2:DescribeVpcs",
          "ec2:DescribeSubnets",
          "ec2:DescribeSecurityGroups",
          "ec2:DescribeVolumes",
          "ec2:DescribeNatGateways",
          "ec2:DescribeInternetGateways",
          "ec2:DescribeRouteTables",
          "ec2:DescribeNetworkAcls",
          "ec2:DescribeNetworkInterfaces",
          "ec2:DescribeAddresses",
          "ec2:DescribeAvailabilityZones",
          "ec2:DescribeVpcPeeringConnections",
          "ec2:DescribeVpcEndpoints",
          "ec2:DescribeManagedPrefixLists",
          "ec2:DescribeAutoScalingGroups"
        ]
        Resource = "*"
      },
      {
        Sid    = "RDSGlobalViewAccess"
        Effect = "Allow"
        Action = [
          "rds:DescribeDBInstances",
          "rds:DescribeDBClusters"
        ]
        Resource = "*"
      },
      {
        Sid    = "S3GlobalViewAccess"
        Effect = "Allow"
        Action = ["s3:ListAllMyBuckets", "s3:GetBucketLocation"]
        Resource = "*"
      },
      {
        Sid    = "ResourceExplorerAccess"
        Effect = "Allow"
        Action = [
          "resource-explorer-2:Search",
          "resource-explorer-2:GetIndex",
          "resource-explorer-2:ListIndexes",
          "resource-explorer-2:ListViews",
          "resource-explorer-2:GetView",
          "resource-explorer-2:ListSupportedResourceTypes"
        ]
        Resource = "*"
      },
      {
        Sid    = "AccountRegionAccess"
        Effect = "Allow"
        Action = [
          "account:GetRegionOptStatus",
          "account:ListRegions"
        ]
        Resource = "*"
      }
    ]
  })
}

resource "aws_iam_role" "global_view_role" {
  name        = "GlobalViewReadOnlyRole"
  description = "IAM Role for Global View access - Lambda or CI/CD"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Effect = "Allow"
        Principal = {
          Service = [
            "lambda.amazonaws.com",
            "ec2.amazonaws.com"
          ]
        }
        Action = "sts:AssumeRole"
      }
    ]
  })
}

resource "aws_iam_role_policy_attachment" "global_view_attach" {
  role       = aws_iam_role.global_view_role.name
  policy_arn = aws_iam_policy.global_view_readonly.arn
}

output "global_view_role_arn" {
  value       = aws_iam_role.global_view_role.arn
  description = "ARN of the Global View read-only IAM role"
}
Enter fullscreen mode Exit fullscreen mode

3.3 — Terraform: Enable Resource Explorer in All Regions

# resource_explorer.tf

# Create the AGGREGATOR index in us-east-1
resource "aws_resourceexplorer2_index" "aggregator" {
  provider = aws.us_east_1
  type     = "AGGREGATOR"

  tags = {
    Name        = "global-aggregator-index"
    ManagedBy   = "Terraform"
    Environment = "production"
  }
}

# Local indexes in each region (must exist before aggregator can pull)
resource "aws_resourceexplorer2_index" "ap_south_1" {
  provider = aws.ap_south_1
  type     = "LOCAL"

  tags = {
    Name      = "local-index-ap-south-1"
    ManagedBy = "Terraform"
  }

  depends_on = [aws_resourceexplorer2_index.aggregator]
}

resource "aws_resourceexplorer2_index" "us_west_2" {
  provider = aws.us_west_2
  type     = "LOCAL"

  tags = {
    Name      = "local-index-us-west-2"
    ManagedBy = "Terraform"
  }

  depends_on = [aws_resourceexplorer2_index.aggregator]
}
Enter fullscreen mode Exit fullscreen mode

3.4 — Terraform: Create Resource Explorer Views

# views.tf

resource "aws_resourceexplorer2_view" "all_resources" {
  provider = aws.us_east_1
  name     = "All-Resources-Global"

  included_property {
    name = "tags"
  }

  depends_on = [aws_resourceexplorer2_index.aggregator]

  tags = {
    ManagedBy = "Terraform"
  }
}

resource "aws_resourceexplorer2_view" "ec2_only" {
  provider = aws.us_east_1
  name     = "EC2-Only-Global"

  filters {
    filter_string = "service:ec2"
  }

  included_property {
    name = "tags"
  }

  depends_on = [aws_resourceexplorer2_index.aggregator]
}

# Set as default view in us-east-1
resource "aws_resourceexplorer2_default_view" "default" {
  provider = aws.us_east_1
  view_arn = aws_resourceexplorer2_view.all_resources.arn
}

output "global_view_arn" {
  value = aws_resourceexplorer2_view.all_resources.arn
}
Enter fullscreen mode Exit fullscreen mode

3.5 — Terraform: Cross-Region Data Sources for Inventory

# inventory.tf

data "aws_instances" "us_east_1" {
  provider = aws.us_east_1
  filter {
    name   = "instance-state-name"
    values = ["running", "stopped"]
  }
}

data "aws_instances" "ap_south_1" {
  provider = aws.ap_south_1
  filter {
    name   = "instance-state-name"
    values = ["running", "stopped"]
  }
}

data "aws_vpcs" "us_east_1" {
  provider = aws.us_east_1
}

data "aws_vpcs" "ap_south_1" {
  provider = aws.ap_south_1
}

output "us_east_1_instances" {
  value = {
    ids   = data.aws_instances.us_east_1.ids
    count = length(data.aws_instances.us_east_1.ids)
  }
}

output "ap_south_1_instances" {
  value = {
    ids   = data.aws_instances.ap_south_1.ids
    count = length(data.aws_instances.ap_south_1.ids)
  }
}

output "global_vpc_summary" {
  value = {
    us_east_1_vpcs   = tolist(data.aws_vpcs.us_east_1.ids)
    ap_south_1_vpcs  = tolist(data.aws_vpcs.ap_south_1.ids)
  }
}
Enter fullscreen mode Exit fullscreen mode

Section 4: AWS CDK (Python) — Global View Infrastructure as Code

4.1 — Project Bootstrap

mkdir aws-global-view-cdk && cd aws-global-view-cdk
python3 -m venv .venv
source .venv/bin/activate

pip install aws-cdk-lib constructs boto3

cdk init app --language python

# Install the Resource Explorer L1 constructs
pip install aws-cdk.aws-resourceexplorer2
Enter fullscreen mode Exit fullscreen mode

4.2 — CDK App Entry Point

# app.py
import aws_cdk as cdk
from stacks.global_view_stack import GlobalViewStack
from stacks.resource_explorer_stack import ResourceExplorerStack

app = cdk.App()

# Deploy IAM and Resource Explorer in us-east-1
GlobalViewStack(
    app,
    "GlobalViewStack",
    env=cdk.Environment(region="us-east-1"),
)

ResourceExplorerStack(
    app,
    "ResourceExplorerStack",
    env=cdk.Environment(region="us-east-1"),
)

app.synth()
Enter fullscreen mode Exit fullscreen mode

4.3 — Global View IAM Stack

# stacks/global_view_stack.py
from aws_cdk import (
    Stack,
    aws_iam as iam,
    CfnOutput,
    Tags,
)
from constructs import Construct

class GlobalViewStack(Stack):
    def __init__(self, scope: Construct, construct_id: str, **kwargs):
        super().__init__(scope, construct_id, **kwargs)

        # Read-only policy for Global View
        policy = iam.ManagedPolicy(
            self,
            "GlobalViewPolicy",
            managed_policy_name="GlobalViewReadOnlyPolicy",
            statements=[
                iam.PolicyStatement(
                    sid="EC2GlobalView",
                    effect=iam.Effect.ALLOW,
                    actions=[
                        "ec2:DescribeRegions",
                        "ec2:DescribeInstances",
                        "ec2:DescribeVpcs",
                        "ec2:DescribeSubnets",
                        "ec2:DescribeSecurityGroups",
                        "ec2:DescribeVolumes",
                        "ec2:DescribeNatGateways",
                        "ec2:DescribeInternetGateways",
                        "ec2:DescribeRouteTables",
                        "ec2:DescribeNetworkAcls",
                        "ec2:DescribeAddresses",
                        "ec2:DescribeAvailabilityZones",
                        "ec2:DescribeVpcPeeringConnections",
                        "ec2:DescribeVpcEndpoints",
                    ],
                    resources=["*"],
                ),
                iam.PolicyStatement(
                    sid="RDSAndS3Access",
                    effect=iam.Effect.ALLOW,
                    actions=[
                        "rds:DescribeDBInstances",
                        "rds:DescribeDBClusters",
                        "s3:ListAllMyBuckets",
                        "s3:GetBucketLocation",
                    ],
                    resources=["*"],
                ),
                iam.PolicyStatement(
                    sid="ResourceExplorerSearch",
                    effect=iam.Effect.ALLOW,
                    actions=[
                        "resource-explorer-2:Search",
                        "resource-explorer-2:GetIndex",
                        "resource-explorer-2:ListIndexes",
                        "resource-explorer-2:ListViews",
                        "resource-explorer-2:GetView",
                    ],
                    resources=["*"],
                ),
                iam.PolicyStatement(
                    sid="AccountAccess",
                    effect=iam.Effect.ALLOW,
                    actions=[
                        "account:GetRegionOptStatus",
                        "account:ListRegions",
                    ],
                    resources=["*"],
                ),
            ],
        )

        # IAM Role for Lambda or automation to use Global View
        role = iam.Role(
            self,
            "GlobalViewRole",
            role_name="GlobalViewReadOnlyRole",
            assumed_by=iam.CompositePrincipal(
                iam.ServicePrincipal("lambda.amazonaws.com"),
                iam.ServicePrincipal("ec2.amazonaws.com"),
            ),
            managed_policies=[
                policy,
                iam.ManagedPolicy.from_aws_managed_policy_name(
                    "service-role/AWSLambdaBasicExecutionRole"
                ),
            ],
        )

        Tags.of(self).add("ManagedBy", "CDK")
        Tags.of(self).add("Purpose", "GlobalView")

        CfnOutput(self, "RoleArn", value=role.role_arn)
        CfnOutput(self, "PolicyArn", value=policy.managed_policy_arn)
Enter fullscreen mode Exit fullscreen mode

4.4 — Resource Explorer CDK Stack

# stacks/resource_explorer_stack.py
from aws_cdk import (
    Stack,
    aws_resourceexplorer2 as rex,
    CfnOutput,
)
from constructs import Construct

class ResourceExplorerStack(Stack):
    def __init__(self, scope: Construct, construct_id: str, **kwargs):
        super().__init__(scope, construct_id, **kwargs)

        # Aggregator index in us-east-1
        aggregator_index = rex.CfnIndex(
            self,
            "AggregatorIndex",
            type="AGGREGATOR",
            tags=[{"key": "ManagedBy", "value": "CDK"}],
        )

        # Global view — all resources with tags
        global_view = rex.CfnView(
            self,
            "AllResourcesView",
            view_name="All-Resources-Global-CDK",
            included_properties=[
                rex.CfnView.IncludedPropertyProperty(name="tags")
            ],
            depends_on=[aggregator_index],
        )

        # EC2-scoped view
        ec2_view = rex.CfnView(
            self,
            "EC2OnlyView",
            view_name="EC2-Only-Global-CDK",
            filters=rex.CfnView.FiltersProperty(
                filter_string="service:ec2"
            ),
            included_properties=[
                rex.CfnView.IncludedPropertyProperty(name="tags")
            ],
            depends_on=[aggregator_index],
        )

        # Set default view
        rex.CfnDefaultViewAssociation(
            self,
            "DefaultView",
            view_arn=global_view.attr_view_arn,
        )

        CfnOutput(self, "AggregatorIndexArn",
                  value=aggregator_index.attr_arn)
        CfnOutput(self, "GlobalViewArn",
                  value=global_view.attr_view_arn)
        CfnOutput(self, "EC2ViewArn",
                  value=ec2_view.attr_view_arn)
Enter fullscreen mode Exit fullscreen mode

Section 5: Python (boto3) — Build Your Own Global View

5.1 — Get All Enabled Regions

import boto3

def get_enabled_regions() -> list[str]:
    """Returns all enabled (opted-in or default) regions."""
    ec2 = boto3.client("ec2", region_name="us-east-1")
    response = ec2.describe_regions(
        Filters=[{
            "Name": "opt-in-status",
            "Values": ["opt-in-not-required", "opted-in"]
        }]
    )
    return sorted([r["RegionName"] for r in response["Regions"]])

if __name__ == "__main__":
    regions = get_enabled_regions()
    print(f"Enabled regions ({len(regions)}): {regions}")
Enter fullscreen mode Exit fullscreen mode

5.2 — Multi-Threaded Global EC2 Inventory

Using ThreadPoolExecutor mimics how Global View fetches all regions in parallel — critical for performance with 20+ regions:

import boto3
import json
from concurrent.futures import ThreadPoolExecutor, as_completed
from datetime import datetime

def get_instances_in_region(region: str) -> list[dict]:
    """Paginated EC2 instance fetch for a single region."""
    ec2 = boto3.client("ec2", region_name=region)
    paginator = ec2.get_paginator("describe_instances")
    instances = []
    try:
        for page in paginator.paginate():
            for reservation in page["Reservations"]:
                for inst in reservation["Instances"]:
                    # Extract Name tag
                    name = next(
                        (t["Value"] for t in inst.get("Tags", [])
                         if t["Key"] == "Name"),
                        "N/A"
                    )
                    instances.append({
                        "Region": region,
                        "InstanceId": inst["InstanceId"],
                        "Name": name,
                        "State": inst["State"]["Name"],
                        "InstanceType": inst["InstanceType"],
                        "PrivateIp": inst.get("PrivateIpAddress", "N/A"),
                        "PublicIp": inst.get("PublicIpAddress", "N/A"),
                        "LaunchTime": inst["LaunchTime"].strftime("%Y-%m-%d %H:%M:%S"),
                        "VpcId": inst.get("VpcId", "N/A"),
                    })
    except Exception as e:
        print(f"[ERROR] {region}: {e}")
    return instances

def global_ec2_inventory(max_workers: int = 15) -> list[dict]:
    regions = get_enabled_regions()
    all_instances = []
    print(f"Scanning {len(regions)} regions with {max_workers} threads...")

    with ThreadPoolExecutor(max_workers=max_workers) as executor:
        future_map = {
            executor.submit(get_instances_in_region, r): r
            for r in regions
        }
        for future in as_completed(future_map):
            region = future_map[future]
            result = future.result()
            count = len(result)
            if count > 0:
                print(f"{region}: {count} instance(s)")
            else:
                print(f"{region}: 0 instances")
            all_instances.extend(result)

    return all_instances

if __name__ == "__main__":
    inventory = global_ec2_inventory()
    print(f"\nTotal instances found: {len(inventory)}")
Enter fullscreen mode Exit fullscreen mode

5.3 — Full Global Resource Summary (Global View Equivalent)

This function replicates the "Summary" section at the top of the Region Explorer console:

import boto3
from concurrent.futures import ThreadPoolExecutor, as_completed

def get_region_summary(region: str) -> dict:
    """Collect resource counts for a single region."""
    ec2 = boto3.client("ec2", region_name=region)
    rds = boto3.client("rds", region_name=region)
    summary = {
        "Region": region,
        "Instances": 0,
        "VPCs": 0,
        "Volumes": 0,
        "SecurityGroups": 0,
        "Subnets": 0,
        "NatGateways": 0,
        "InternetGateways": 0,
        "ElasticIPs": 0,
        "DBInstances": 0,
        "Error": None,
    }
    try:
        summary["Instances"] = sum(
            len(r["Instances"])
            for r in ec2.describe_instances()["Reservations"]
        )
        summary["VPCs"] = len(ec2.describe_vpcs()["Vpcs"])
        summary["Volumes"] = len(ec2.describe_volumes()["Volumes"])
        summary["SecurityGroups"] = len(
            ec2.describe_security_groups()["SecurityGroups"]
        )
        summary["Subnets"] = len(ec2.describe_subnets()["Subnets"])
        summary["NatGateways"] = len(
            ec2.describe_nat_gateways(
                Filters=[{"Name": "state", "Values": ["available"]}]
            )["NatGateways"]
        )
        summary["InternetGateways"] = len(
            ec2.describe_internet_gateways()["InternetGateways"]
        )
        summary["ElasticIPs"] = len(
            ec2.describe_addresses()["Addresses"]
        )
        summary["DBInstances"] = len(
            rds.describe_db_instances()["DBInstances"]
        )
    except Exception as e:
        summary["Error"] = str(e)
    return summary

def generate_global_view_report() -> list[dict]:
    regions = get_enabled_regions()
    report = []
    with ThreadPoolExecutor(max_workers=15) as executor:
        futures = {
            executor.submit(get_region_summary, r): r
            for r in regions
        }
        for future in as_completed(futures):
            result = future.result()
            report.append(result)

    # Sort by instance count descending (like Global View's default)
    report.sort(key=lambda x: x.get("Instances", 0), reverse=True)
    return report
Enter fullscreen mode Exit fullscreen mode

5.4 — Export Report to CSV

import csv
from datetime import datetime

def export_to_csv(report: list[dict], filename: str = None):
    if not filename:
        ts = datetime.now().strftime("%Y%m%d_%H%M%S")
        filename = f"global_view_report_{ts}.csv"

    fields = [
        "Region", "Instances", "VPCs", "Volumes",
        "SecurityGroups", "Subnets", "NatGateways",
        "InternetGateways", "ElasticIPs", "DBInstances", "Error"
    ]

    with open(filename, "w", newline="") as f:
        writer = csv.DictWriter(f, fieldnames=fields, extrasaction="ignore")
        writer.writeheader()
        writer.writerows(report)

    print(f"✅ Report exported: {filename}")
    return filename

# Run full pipeline
report = generate_global_view_report()
export_to_csv(report)
Enter fullscreen mode Exit fullscreen mode

5.5 — Security Audit: Find Open Security Groups Globally

def find_open_security_groups(port: int = 22) -> list[dict]:
    """
    Scans all regions for security groups with 0.0.0.0/0 access
    on the specified port. Default: SSH (22). Common: RDP (3389).
    """
    def scan_region(region):
        ec2 = boto3.client("ec2", region_name=region)
        risky_sgs = []
        try:
            sgs = ec2.describe_security_groups(
                Filters=[
                    {"Name": "ip-permission.from-port", "Values": [str(port)]},
                    {"Name": "ip-permission.to-port", "Values": [str(port)]},
                    {"Name": "ip-permission.cidr", "Values": ["0.0.0.0/0"]},
                ]
            )["SecurityGroups"]

            for sg in sgs:
                risky_sgs.append({
                    "Region": region,
                    "GroupId": sg["GroupId"],
                    "GroupName": sg["GroupName"],
                    "VpcId": sg.get("VpcId", "EC2-Classic"),
                    "Description": sg.get("Description", ""),
                })
        except Exception:
            pass
        return risky_sgs

    regions = get_enabled_regions()
    all_risky = []
    with ThreadPoolExecutor(max_workers=15) as executor:
        results = executor.map(scan_region, regions)
        for result in results:
            all_risky.extend(result)

    print(f"⚠️  Found {len(all_risky)} security groups with port {port} open to 0.0.0.0/0")
    return all_risky

# Find SSH exposure
ssh_exposed = find_open_security_groups(port=22)
rdp_exposed = find_open_security_groups(port=3389)
Enter fullscreen mode Exit fullscreen mode

5.6 — FinOps: Find Orphaned Resources Globally

def find_orphaned_resources() -> dict:
    """
    Identifies unattached EBS volumes and unassociated Elastic IPs
    across all regions — common sources of unnecessary AWS spend.
    """
    def scan_region(region):
        ec2 = boto3.client("ec2", region_name=region)
        orphans = {"region": region, "unattached_volumes": [], "unused_eips": []}
        try:
            # Unattached EBS volumes (status = available)
            vols = ec2.describe_volumes(
                Filters=[{"Name": "status", "Values": ["available"]}]
            )["Volumes"]
            orphans["unattached_volumes"] = [
                {
                    "VolumeId": v["VolumeId"],
                    "SizeGB": v["Size"],
                    "Type": v["VolumeType"],
                    "EstimatedMonthlyCost": round(v["Size"] * 0.10, 2),  # ~$0.10/GB-month gp2
                }
                for v in vols
            ]

            # Elastic IPs not associated with any instance
            eips = ec2.describe_addresses()["Addresses"]
            orphans["unused_eips"] = [
                {
                    "AllocationId": e.get("AllocationId"),
                    "PublicIp": e["PublicIp"],
                    "EstimatedMonthlyCost": 3.65,  # ~$0.005/hour idle EIP
                }
                for e in eips
                if "AssociationId" not in e
            ]
        except Exception:
            pass
        return orphans

    regions = get_enabled_regions()
    results = {}
    total_vol_cost = 0
    total_eip_cost = 0

    with ThreadPoolExecutor(max_workers=15) as executor:
        for result in executor.map(scan_region, regions):
            region = result["region"]
            if result["unattached_volumes"] or result["unused_eips"]:
                results[region] = result
                total_vol_cost += sum(
                    v["EstimatedMonthlyCost"]
                    for v in result["unattached_volumes"]
                )
                total_eip_cost += sum(
                    e["EstimatedMonthlyCost"]
                    for e in result["unused_eips"]
                )

    print(f"\n💸 Estimated monthly waste:")
    print(f"   Unattached volumes: ${total_vol_cost:.2f}/month")
    print(f"   Unused Elastic IPs: ${total_eip_cost:.2f}/month")
    print(f"   Total:              ${total_vol_cost + total_eip_cost:.2f}/month")
    return results

orphans = find_orphaned_resources()
Enter fullscreen mode Exit fullscreen mode

5.7 — Resource Explorer Python Client

Using the resource-explorer-2 boto3 client to search cross-region programmatically:

import boto3

def resource_explorer_search(query: str, view_arn: str = None) -> list[dict]:
    """
    Search resources using AWS Resource Explorer v2 API.
    Requires an aggregator index in us-east-1 to be active.
    """
    client = boto3.client("resource-explorer-2", region_name="us-east-1")
    kwargs = {"QueryString": query}
    if view_arn:
        kwargs["ViewArn"] = view_arn

    resources = []
    paginator = client.get_paginator("search")
    try:
        for page in paginator.paginate(**kwargs):
            for resource in page.get("Resources", []):
                resources.append({
                    "Arn": resource["Arn"],
                    "Region": resource["Region"],
                    "ResourceType": resource["ResourceType"],
                    "Service": resource["Service"],
                    "LastReportedAt": str(resource.get("LastReportedAt", "")),
                    "Tags": {
                        tag["Key"]: tag["Value"]
                        for prop in resource.get("Properties", [])
                        if prop["Name"] == "tags"
                        for tag in prop.get("Data", [])
                    },
                })
    except client.exceptions.UnauthorizedException:
        print("❌ Resource Explorer not enabled or no aggregator index found.")
    return resources

# Search all untagged EC2 instances
untagged = resource_explorer_search(
    query="resourcetype:ec2:instance -tag.Environment"
)
print(f"Untagged EC2 instances: {len(untagged)}")

# Search production resources by tag
prod_resources = resource_explorer_search(
    query="tag.Environment=production"
)
print(f"Production-tagged resources: {len(prod_resources)}")
Enter fullscreen mode Exit fullscreen mode

Section 6: Automate Global View Reports with Lambda

Deploy a daily Lambda function that generates a full Global View report and sends a cost-warning alert via SNS if waste is detected:

# lambda_function.py
import boto3
import json
import os
from concurrent.futures import ThreadPoolExecutor, as_completed
from datetime import datetime

SNS_TOPIC_ARN = os.environ.get("SNS_TOPIC_ARN")
S3_BUCKET = os.environ.get("REPORT_BUCKET")
WASTE_THRESHOLD_USD = float(os.environ.get("WASTE_THRESHOLD", "50"))

def lambda_handler(event, context):
    regions = get_enabled_regions()
    report = []
    total_waste = 0.0

    with ThreadPoolExecutor(max_workers=15) as executor:
        futures = {
            executor.submit(get_region_summary, r): r
            for r in regions
        }
        for future in as_completed(futures):
            result = future.result()
            report.append(result)

    # Sort by instances descending
    report.sort(key=lambda x: x.get("Instances", 0), reverse=True)

    # Calculate total resources
    total_instances = sum(r.get("Instances", 0) for r in report)
    total_vpcs = sum(r.get("VPCs", 0) for r in report)
    total_volumes = sum(r.get("Volumes", 0) for r in report)

    # Save to S3
    ts = datetime.utcnow().strftime("%Y/%m/%d")
    key = f"global-view-reports/{ts}/report.json"
    s3 = boto3.client("s3")
    s3.put_object(
        Bucket=S3_BUCKET,
        Key=key,
        Body=json.dumps(report, default=str),
        ContentType="application/json",
    )
    print(f"Report saved to s3://{S3_BUCKET}/{key}")

    # SNS alert if waste exceeds threshold
    if total_waste > WASTE_THRESHOLD_USD and SNS_TOPIC_ARN:
        sns = boto3.client("sns")
        sns.publish(
            TopicArn=SNS_TOPIC_ARN,
            Subject="⚠️ AWS Global View - Waste Alert",
            Message=(
                f"Estimated monthly waste detected: ${total_waste:.2f}\n\n"
                f"Global Summary:\n"
                f"  Regions scanned: {len(regions)}\n"
                f"  Total EC2 Instances: {total_instances}\n"
                f"  Total VPCs: {total_vpcs}\n"
                f"  Total EBS Volumes: {total_volumes}\n\n"
                f"Full report: s3://{S3_BUCKET}/{key}"
            ),
        )

    return {
        "statusCode": 200,
        "regions_scanned": len(regions),
        "total_instances": total_instances,
        "total_vpcs": total_vpcs,
        "report_location": f"s3://{S3_BUCKET}/{key}",
    }
Enter fullscreen mode Exit fullscreen mode

Schedule it with EventBridge:

# Create EventBridge rule — runs every day at 7 AM UTC
aws events put-rule \
  --name "GlobalViewDailyReport" \
  --schedule-expression "cron(0 7 * * ? *)" \
  --state ENABLED

# Attach Lambda target
aws events put-targets \
  --rule "GlobalViewDailyReport" \
  --targets "Id=GlobalViewLambda,Arn=arn:aws:lambda:us-east-1:123456789012:function:GlobalViewReport"
Enter fullscreen mode Exit fullscreen mode

Section 7: Real-World Use Cases

Here's where AWS Global View (and its programmatic equivalents) deliver real production value:

  • Disaster Recovery Audit: Verify that standby EC2 instances and replicated RDS clusters exist in your DR region before a failover test
  • FinOps & Cost Optimization: Run the orphaned resource script monthly — unattached volumes and unused EIPs silently drain budgets in forgotten regions
  • Security Compliance: Use the open-SG scanner to detect SSH/RDP exposure globally; integrate into your CI/CD pipeline as a pre-deployment gate
  • Tag Enforcement: Use Resource Explorer's -tag.CostCenter query to find all resources missing mandatory billing tags — critical before month-end chargebacks
  • Region Governance: Disable opt-in regions your org never uses to reduce your attack surface and avoid accidental deployments
  • Migration Validation: After an AWS region migration, use the global inventory to confirm zero resources remain in the source region

Section 8: Limitations & Expert Best Practices

Limitations

  • Global View is read-only — pair it with AWS Config Rules for policy enforcement
  • It covers only EC2/VPC resource families — Lambda functions, ECS clusters, CloudFront distributions are NOT shown in Global View (use Resource Explorer for those)
  • Resource Explorer requires an aggregator index in us-east-1 and local indexes in all other regions you want to cover — this is not free but is very inexpensive
  • boto3 pagination is mandatory for accounts with >1,000 resources per region — always use paginators, never raw API calls with default limits

Expert Best Practices

  • Always use ThreadPoolExecutor with max_workers=15 (AWS default rate limits allow ~20 concurrent describe calls per region per account)
  • Cache region lists in SSM Parameter Store or DynamoDB — describe_regions adds latency and counts against API quotas when called thousands of times daily
  • Tag everything — Global View and Resource Explorer's tag-based search is useless without a consistent tagging strategy; enforce tags using SCP (Service Control Policies) at the org level
  • Combine with AWS Config — Global View tells you what exists; AWS Config tells you if it complies with your policies
  • Use Terraform remote state for multi-region setups — store state in S3 with DynamoDB locking, one state file per region per environment
  • Set AGGREGATOR index in us-east-1 — all Resource Explorer search calls must target the aggregator region; calls to local-only index regions return only local results

Conclusion

AWS Global View is one of AWS's most underutilized yet immediately actionable tools. In the console it gives you instant multi-region visibility with zero setup. Paired with the AWS CLI for scripting, Terraform and CDK for IaC, and boto3 for automation, you can build a fully programmatic, always-fresh global inventory system that does far more than what the console offers — including cost waste detection, security audits, compliance reporting, and scheduled alerting.

Top comments (0)