Skip to content

Commit 61e7ea6

Browse files
committed
Redesign HTML report and add --exclude-defaults option
UI: - Dashboard charts (Top Services, Top Regions), stat cards, and service section counts now update dynamically when filters change - Align visual style with AWS Console (Cloudscape Design System): color palette (#0972d3 blue, #ec7211 orange, #232f3e header), flat solid bars, bordered cards, restrained color use - Compact Cloudscape-standard spacing: 14px base font, 1.43 line-height, 10px 12px table cells, 16px card padding, 4px border-radius - Tags styled as outlined badges instead of gradient pills - Primary buttons use AWS orange, secondary buttons use bordered style - Improved mobile layout with 2-column stat grid - Default resources now show a "DEFAULT" badge in HTML reports - Report subtitle changed to "Comprehensive AWS Asset Discovery" --exclude-defaults: - Add --exclude-defaults CLI flag to filter out AWS-created default resources (default VPCs, subnets, security groups, route tables, internet gateways, NACLs, DHCP options) - Collectors mark resources with top-level is_default field; CLI filters post-collection (same pattern as tag filtering) - CSV output includes is_default column Bug fix: - Add missing aiobotocore dependency to pyproject.toml, fixing Lambda collector silently not loading
1 parent 8ff285d commit 61e7ea6

8 files changed

Lines changed: 273 additions & 159 deletions

File tree

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ FROM python:3.11-slim
22

33
LABEL maintainer="Tarek CHEIKH <tarek@tocconsulting.fr>"
44
LABEL description="A fast, comprehensive tool for mapping and inventorying AWS resources across 150+ services"
5-
LABEL version="1.3.0"
5+
LABEL version="1.4.0"
66

77
# Set environment variables
88
ENV PYTHONDONTWRITEBYTECODE=1

README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,9 @@ awsmap --list-services
102102

103103
# Show timing per service (useful for debugging)
104104
awsmap -p myprofile --timings
105+
106+
# Exclude default AWS resources (default VPCs, security groups, etc.)
107+
awsmap -p myprofile --exclude-defaults
105108
```
106109

107110
## CLI Options
@@ -118,6 +121,7 @@ awsmap -p myprofile --timings
118121
| `-q, --quiet` | Suppress progress output |
119122
| `--timings` | Show timing summary per service |
120123
| `--include-global` | Include global services when filtering by non-global regions |
124+
| `--exclude-defaults` | Exclude default AWS resources (default VPCs, security groups, etc.) |
121125
| `--list-services` | List available service collectors |
122126

123127
## Supported Services
@@ -180,7 +184,7 @@ Interactive report with:
180184
```
181185

182186
### CSV
183-
Flat format with columns: service, type, id, name, region, arn, tags
187+
Flat format with columns: service, type, id, name, region, arn, is_default, tags
184188

185189
## Tag Filtering
186190

@@ -308,6 +312,8 @@ This tool only collects **user-owned resources**, excluding:
308312
- AWS managed domain lists (Route53 Resolver: `AWSManagedDomains*`)
309313
- Default data lake settings (Lake Formation)
310314

315+
**Default VPC resources** (default VPCs, subnets, security groups, route tables, internet gateways, NACLs, DHCP options) are collected by default and marked with a "DEFAULT" badge in HTML reports. Use `--exclude-defaults` to filter them out.
316+
311317
See [SERVICES.md](SERVICES.md#filtered-resources) for the complete list of filtered resources.
312318

313319
## Support

pyproject.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "awsmap"
7-
version = "1.3.0"
7+
version = "1.4.0"
88
description = "A fast, comprehensive tool for mapping and inventorying AWS resources across 150+ services and all regions"
99
readme = "README.md"
1010
requires-python = ">=3.8"
@@ -33,6 +33,7 @@ dependencies = [
3333
"click>=8.0.0",
3434
"pyyaml>=6.0.0",
3535
"botocore[crt]>=1.29.0", # For aws login credential support
36+
"aiobotocore>=2.5.0",
3637
]
3738

3839
[project.urls]

src/aws_inventory/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
awsmap - Fast, comprehensive AWS resource inventory across 150+ services.
33
"""
44

5-
__version__ = '1.3.0'
5+
__version__ = '1.4.0'

src/aws_inventory/cli.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ def print_progress(service: str, status: str) -> None:
2929
@click.option('--quiet', '-q', is_flag=True, help='Suppress progress output')
3030
@click.option('--timings', is_flag=True, help='Show timing summary per service')
3131
@click.option('--include-global', is_flag=True, help='Include global services even when filtering by non-global regions')
32+
@click.option('--exclude-defaults', is_flag=True, help='Exclude default AWS resources (default VPCs, security groups, etc.)')
3233
def main(
3334
profile: Optional[str],
3435
region: tuple,
@@ -40,7 +41,8 @@ def main(
4041
tag: tuple,
4142
quiet: bool,
4243
timings: bool,
43-
include_global: bool
44+
include_global: bool,
45+
exclude_defaults: bool
4446
) -> None:
4547
"""
4648
awsmap - Map and inventory AWS resources.
@@ -172,6 +174,12 @@ def main(
172174
result['metadata']['resource_count'] = len(filtered_resources)
173175
result['metadata']['tag_filter'] = tag_filters
174176

177+
# Exclude default AWS resources
178+
if exclude_defaults:
179+
result['resources'] = [r for r in result['resources'] if not r.get('is_default')]
180+
result['metadata']['resource_count'] = len(result['resources'])
181+
result['metadata']['exclude_defaults'] = True
182+
175183
# Summary
176184
if not quiet:
177185
click.echo("-" * 40)

src/aws_inventory/collectors/ec2.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ def collect_ec2_resources(session: boto3.Session, region: Optional[str], account
146146
'arn': f"arn:aws:ec2:{region}:{account_id}:security-group/{sg['GroupId']}",
147147
'name': sg.get('GroupName') or sg['GroupId'],
148148
'region': region,
149+
'is_default': sg.get('GroupName') == 'default',
149150
'details': {
150151
'vpc_id': sg.get('VpcId'),
151152
'description': sg.get('Description'),

src/aws_inventory/collectors/vpc.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ def collect_vpc_resources(session: boto3.Session, region: Optional[str], account
3737
'arn': f"arn:aws:ec2:{region}:{account_id}:vpc/{vpc['VpcId']}",
3838
'name': get_tag_value(tags, 'Name') or vpc['VpcId'],
3939
'region': region,
40+
'is_default': vpc.get('IsDefault', False),
4041
'details': {
4142
'cidr_block': vpc.get('CidrBlock'),
4243
'state': vpc.get('State'),
@@ -49,6 +50,10 @@ def collect_vpc_resources(session: boto3.Session, region: Optional[str], account
4950
except Exception:
5051
pass
5152

53+
# Build set of default VPC IDs and their DHCP options for marking child resources
54+
default_vpc_ids = {r['id'] for r in resources if r.get('is_default')}
55+
default_dhcp_ids = {r['details']['dhcp_options_id'] for r in resources if r.get('is_default') and r['details'].get('dhcp_options_id')}
56+
5257
# Subnets
5358
try:
5459
paginator = ec2.get_paginator('describe_subnets')
@@ -62,6 +67,7 @@ def collect_vpc_resources(session: boto3.Session, region: Optional[str], account
6267
'arn': subnet.get('SubnetArn', f"arn:aws:ec2:{region}:{account_id}:subnet/{subnet['SubnetId']}"),
6368
'name': get_tag_value(tags, 'Name') or subnet['SubnetId'],
6469
'region': region,
70+
'is_default': subnet.get('DefaultForAz', False),
6571
'details': {
6672
'vpc_id': subnet.get('VpcId'),
6773
'cidr_block': subnet.get('CidrBlock'),
@@ -82,18 +88,20 @@ def collect_vpc_resources(session: boto3.Session, region: Optional[str], account
8288
for page in paginator.paginate():
8389
for rt in page.get('RouteTables', []):
8490
tags = rt.get('Tags', [])
91+
is_main = any(a.get('Main') for a in rt.get('Associations', []))
8592
resources.append({
8693
'service': 'vpc',
8794
'type': 'route-table',
8895
'id': rt['RouteTableId'],
8996
'arn': f"arn:aws:ec2:{region}:{account_id}:route-table/{rt['RouteTableId']}",
9097
'name': get_tag_value(tags, 'Name') or rt['RouteTableId'],
9198
'region': region,
99+
'is_default': rt.get('VpcId') in default_vpc_ids and is_main,
92100
'details': {
93101
'vpc_id': rt.get('VpcId'),
94102
'routes_count': len(rt.get('Routes', [])),
95103
'associations_count': len(rt.get('Associations', [])),
96-
'main': any(a.get('Main') for a in rt.get('Associations', [])),
104+
'main': is_main,
97105
},
98106
'tags': tags_to_dict(tags)
99107
})
@@ -114,6 +122,7 @@ def collect_vpc_resources(session: boto3.Session, region: Optional[str], account
114122
'arn': f"arn:aws:ec2:{region}:{account_id}:internet-gateway/{igw['InternetGatewayId']}",
115123
'name': get_tag_value(tags, 'Name') or igw['InternetGatewayId'],
116124
'region': region,
125+
'is_default': (attachments[0].get('VpcId') in default_vpc_ids) if attachments else False,
117126
'details': {
118127
'vpc_id': attachments[0].get('VpcId') if attachments else None,
119128
'state': attachments[0].get('State') if attachments else 'detached',
@@ -280,6 +289,7 @@ def collect_vpc_resources(session: boto3.Session, region: Optional[str], account
280289
'arn': f"arn:aws:ec2:{region}:{account_id}:dhcp-options/{dhcp['DhcpOptionsId']}",
281290
'name': get_tag_value(tags, 'Name') or dhcp['DhcpOptionsId'],
282291
'region': region,
292+
'is_default': dhcp['DhcpOptionsId'] in default_dhcp_ids,
283293
'details': {
284294
'configurations': {cfg['Key']: cfg.get('Values', []) for cfg in dhcp.get('DhcpConfigurations', [])},
285295
},
@@ -301,6 +311,7 @@ def collect_vpc_resources(session: boto3.Session, region: Optional[str], account
301311
'arn': f"arn:aws:ec2:{region}:{account_id}:network-acl/{nacl['NetworkAclId']}",
302312
'name': get_tag_value(tags, 'Name') or nacl['NetworkAclId'],
303313
'region': region,
314+
'is_default': nacl.get('IsDefault', False),
304315
'details': {
305316
'vpc_id': nacl.get('VpcId'),
306317
'is_default': nacl.get('IsDefault'),

0 commit comments

Comments
 (0)