aws-inventory-manager 0.17.12__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. aws_inventory_manager-0.17.12.dist-info/LICENSE +21 -0
  2. aws_inventory_manager-0.17.12.dist-info/METADATA +1292 -0
  3. aws_inventory_manager-0.17.12.dist-info/RECORD +152 -0
  4. aws_inventory_manager-0.17.12.dist-info/WHEEL +5 -0
  5. aws_inventory_manager-0.17.12.dist-info/entry_points.txt +2 -0
  6. aws_inventory_manager-0.17.12.dist-info/top_level.txt +1 -0
  7. src/__init__.py +3 -0
  8. src/aws/__init__.py +11 -0
  9. src/aws/client.py +128 -0
  10. src/aws/credentials.py +191 -0
  11. src/aws/rate_limiter.py +177 -0
  12. src/cli/__init__.py +12 -0
  13. src/cli/config.py +130 -0
  14. src/cli/main.py +4046 -0
  15. src/cloudtrail/__init__.py +5 -0
  16. src/cloudtrail/query.py +642 -0
  17. src/config_service/__init__.py +21 -0
  18. src/config_service/collector.py +346 -0
  19. src/config_service/detector.py +256 -0
  20. src/config_service/resource_type_mapping.py +328 -0
  21. src/cost/__init__.py +5 -0
  22. src/cost/analyzer.py +226 -0
  23. src/cost/explorer.py +209 -0
  24. src/cost/reporter.py +237 -0
  25. src/delta/__init__.py +5 -0
  26. src/delta/calculator.py +206 -0
  27. src/delta/differ.py +185 -0
  28. src/delta/formatters.py +272 -0
  29. src/delta/models.py +154 -0
  30. src/delta/reporter.py +234 -0
  31. src/matching/__init__.py +6 -0
  32. src/matching/config.py +52 -0
  33. src/matching/normalizer.py +450 -0
  34. src/matching/prompts.py +33 -0
  35. src/models/__init__.py +21 -0
  36. src/models/config_diff.py +135 -0
  37. src/models/cost_report.py +87 -0
  38. src/models/deletion_operation.py +104 -0
  39. src/models/deletion_record.py +97 -0
  40. src/models/delta_report.py +122 -0
  41. src/models/efs_resource.py +80 -0
  42. src/models/elasticache_resource.py +90 -0
  43. src/models/group.py +318 -0
  44. src/models/inventory.py +133 -0
  45. src/models/protection_rule.py +123 -0
  46. src/models/report.py +288 -0
  47. src/models/resource.py +111 -0
  48. src/models/security_finding.py +102 -0
  49. src/models/snapshot.py +122 -0
  50. src/restore/__init__.py +20 -0
  51. src/restore/audit.py +175 -0
  52. src/restore/cleaner.py +461 -0
  53. src/restore/config.py +209 -0
  54. src/restore/deleter.py +976 -0
  55. src/restore/dependency.py +254 -0
  56. src/restore/safety.py +115 -0
  57. src/security/__init__.py +0 -0
  58. src/security/checks/__init__.py +0 -0
  59. src/security/checks/base.py +56 -0
  60. src/security/checks/ec2_checks.py +88 -0
  61. src/security/checks/elasticache_checks.py +149 -0
  62. src/security/checks/iam_checks.py +102 -0
  63. src/security/checks/rds_checks.py +140 -0
  64. src/security/checks/s3_checks.py +95 -0
  65. src/security/checks/secrets_checks.py +96 -0
  66. src/security/checks/sg_checks.py +142 -0
  67. src/security/cis_mapper.py +97 -0
  68. src/security/models.py +53 -0
  69. src/security/reporter.py +174 -0
  70. src/security/scanner.py +87 -0
  71. src/snapshot/__init__.py +6 -0
  72. src/snapshot/capturer.py +453 -0
  73. src/snapshot/filter.py +259 -0
  74. src/snapshot/inventory_storage.py +236 -0
  75. src/snapshot/report_formatter.py +250 -0
  76. src/snapshot/reporter.py +189 -0
  77. src/snapshot/resource_collectors/__init__.py +5 -0
  78. src/snapshot/resource_collectors/apigateway.py +140 -0
  79. src/snapshot/resource_collectors/backup.py +136 -0
  80. src/snapshot/resource_collectors/base.py +81 -0
  81. src/snapshot/resource_collectors/cloudformation.py +55 -0
  82. src/snapshot/resource_collectors/cloudwatch.py +109 -0
  83. src/snapshot/resource_collectors/codebuild.py +69 -0
  84. src/snapshot/resource_collectors/codepipeline.py +82 -0
  85. src/snapshot/resource_collectors/dynamodb.py +65 -0
  86. src/snapshot/resource_collectors/ec2.py +240 -0
  87. src/snapshot/resource_collectors/ecs.py +215 -0
  88. src/snapshot/resource_collectors/efs_collector.py +102 -0
  89. src/snapshot/resource_collectors/eks.py +200 -0
  90. src/snapshot/resource_collectors/elasticache_collector.py +79 -0
  91. src/snapshot/resource_collectors/elb.py +126 -0
  92. src/snapshot/resource_collectors/eventbridge.py +156 -0
  93. src/snapshot/resource_collectors/glue.py +199 -0
  94. src/snapshot/resource_collectors/iam.py +188 -0
  95. src/snapshot/resource_collectors/kms.py +111 -0
  96. src/snapshot/resource_collectors/lambda_func.py +139 -0
  97. src/snapshot/resource_collectors/rds.py +109 -0
  98. src/snapshot/resource_collectors/route53.py +86 -0
  99. src/snapshot/resource_collectors/s3.py +105 -0
  100. src/snapshot/resource_collectors/secretsmanager.py +70 -0
  101. src/snapshot/resource_collectors/sns.py +68 -0
  102. src/snapshot/resource_collectors/sqs.py +82 -0
  103. src/snapshot/resource_collectors/ssm.py +160 -0
  104. src/snapshot/resource_collectors/stepfunctions.py +74 -0
  105. src/snapshot/resource_collectors/vpcendpoints.py +79 -0
  106. src/snapshot/resource_collectors/waf.py +159 -0
  107. src/snapshot/storage.py +351 -0
  108. src/storage/__init__.py +21 -0
  109. src/storage/audit_store.py +419 -0
  110. src/storage/database.py +294 -0
  111. src/storage/group_store.py +763 -0
  112. src/storage/inventory_store.py +320 -0
  113. src/storage/resource_store.py +416 -0
  114. src/storage/schema.py +339 -0
  115. src/storage/snapshot_store.py +363 -0
  116. src/utils/__init__.py +12 -0
  117. src/utils/export.py +305 -0
  118. src/utils/hash.py +60 -0
  119. src/utils/logging.py +63 -0
  120. src/utils/pagination.py +41 -0
  121. src/utils/paths.py +51 -0
  122. src/utils/progress.py +41 -0
  123. src/utils/unsupported_resources.py +306 -0
  124. src/web/__init__.py +5 -0
  125. src/web/app.py +97 -0
  126. src/web/dependencies.py +69 -0
  127. src/web/routes/__init__.py +1 -0
  128. src/web/routes/api/__init__.py +18 -0
  129. src/web/routes/api/charts.py +156 -0
  130. src/web/routes/api/cleanup.py +186 -0
  131. src/web/routes/api/filters.py +253 -0
  132. src/web/routes/api/groups.py +305 -0
  133. src/web/routes/api/inventories.py +80 -0
  134. src/web/routes/api/queries.py +202 -0
  135. src/web/routes/api/resources.py +393 -0
  136. src/web/routes/api/snapshots.py +314 -0
  137. src/web/routes/api/views.py +260 -0
  138. src/web/routes/pages.py +198 -0
  139. src/web/services/__init__.py +1 -0
  140. src/web/templates/base.html +955 -0
  141. src/web/templates/components/navbar.html +31 -0
  142. src/web/templates/components/sidebar.html +104 -0
  143. src/web/templates/pages/audit_logs.html +86 -0
  144. src/web/templates/pages/cleanup.html +279 -0
  145. src/web/templates/pages/dashboard.html +227 -0
  146. src/web/templates/pages/diff.html +175 -0
  147. src/web/templates/pages/error.html +30 -0
  148. src/web/templates/pages/groups.html +721 -0
  149. src/web/templates/pages/queries.html +246 -0
  150. src/web/templates/pages/resources.html +2429 -0
  151. src/web/templates/pages/snapshot_detail.html +271 -0
  152. src/web/templates/pages/snapshots.html +429 -0
@@ -0,0 +1,136 @@
1
+ """AWS Backup resource collector."""
2
+
3
+ from typing import List
4
+
5
+ from ...models.resource import Resource
6
+ from ...utils.hash import compute_config_hash
7
+ from .base import BaseResourceCollector
8
+
9
+
10
+ class BackupCollector(BaseResourceCollector):
11
+ """Collector for AWS Backup resources."""
12
+
13
+ @property
14
+ def service_name(self) -> str:
15
+ return "backup"
16
+
17
+ def collect(self) -> List[Resource]:
18
+ """Collect AWS Backup resources.
19
+
20
+ Collects:
21
+ - Backup Plans
22
+ - Backup Vaults
23
+
24
+ Returns:
25
+ List of AWS Backup resources
26
+ """
27
+ resources = []
28
+
29
+ # Collect Backup Plans
30
+ resources.extend(self._collect_backup_plans())
31
+
32
+ # Collect Backup Vaults
33
+ resources.extend(self._collect_backup_vaults())
34
+
35
+ self.logger.debug(f"Collected {len(resources)} AWS Backup resources in {self.region}")
36
+ return resources
37
+
38
+ def _collect_backup_plans(self) -> List[Resource]:
39
+ """Collect Backup Plans.
40
+
41
+ Returns:
42
+ List of Backup Plan resources
43
+ """
44
+ resources = []
45
+ client = self._create_client()
46
+
47
+ try:
48
+ paginator = client.get_paginator("list_backup_plans")
49
+ for page in paginator.paginate():
50
+ for plan_summary in page.get("BackupPlansList", []):
51
+ plan_id = plan_summary["BackupPlanId"]
52
+ plan_name = plan_summary["BackupPlanName"]
53
+ plan_arn = plan_summary["BackupPlanArn"]
54
+
55
+ try:
56
+ # Get detailed plan info
57
+ plan_response = client.get_backup_plan(BackupPlanId=plan_id)
58
+ plan = plan_response.get("BackupPlan", {})
59
+
60
+ # Get tags
61
+ tags = {}
62
+ try:
63
+ tag_response = client.list_tags(ResourceArn=plan_arn)
64
+ tags = tag_response.get("Tags", {})
65
+ except Exception as e:
66
+ self.logger.debug(f"Could not get tags for backup plan {plan_name}: {e}")
67
+
68
+ # Extract creation date
69
+ created_at = plan_summary.get("CreationDate")
70
+
71
+ # Create resource
72
+ resource = Resource(
73
+ arn=plan_arn,
74
+ resource_type="AWS::Backup::BackupPlan",
75
+ name=plan_name,
76
+ region=self.region,
77
+ tags=tags,
78
+ config_hash=compute_config_hash(plan),
79
+ created_at=created_at,
80
+ raw_config=plan,
81
+ )
82
+ resources.append(resource)
83
+
84
+ except Exception as e:
85
+ self.logger.debug(f"Error processing backup plan {plan_name}: {e}")
86
+ continue
87
+
88
+ except Exception as e:
89
+ self.logger.error(f"Error collecting Backup Plans in {self.region}: {e}")
90
+
91
+ return resources
92
+
93
+ def _collect_backup_vaults(self) -> List[Resource]:
94
+ """Collect Backup Vaults.
95
+
96
+ Returns:
97
+ List of Backup Vault resources
98
+ """
99
+ resources = []
100
+ client = self._create_client()
101
+
102
+ try:
103
+ paginator = client.get_paginator("list_backup_vaults")
104
+ for page in paginator.paginate():
105
+ for vault in page.get("BackupVaultList", []):
106
+ vault_name = vault["BackupVaultName"]
107
+ vault_arn = vault["BackupVaultArn"]
108
+
109
+ # Get tags
110
+ tags = {}
111
+ try:
112
+ tag_response = client.list_tags(ResourceArn=vault_arn)
113
+ tags = tag_response.get("Tags", {})
114
+ except Exception as e:
115
+ self.logger.debug(f"Could not get tags for backup vault {vault_name}: {e}")
116
+
117
+ # Extract creation date
118
+ created_at = vault.get("CreationDate")
119
+
120
+ # Create resource
121
+ resource = Resource(
122
+ arn=vault_arn,
123
+ resource_type="AWS::Backup::BackupVault",
124
+ name=vault_name,
125
+ region=self.region,
126
+ tags=tags,
127
+ config_hash=compute_config_hash(vault),
128
+ created_at=created_at,
129
+ raw_config=vault,
130
+ )
131
+ resources.append(resource)
132
+
133
+ except Exception as e:
134
+ self.logger.error(f"Error collecting Backup Vaults in {self.region}: {e}")
135
+
136
+ return resources
@@ -0,0 +1,81 @@
1
+ """Base resource collector interface."""
2
+
3
+ import logging
4
+ from abc import ABC, abstractmethod
5
+ from typing import List, Optional
6
+
7
+ import boto3
8
+
9
+ from ...models.resource import Resource
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ class BaseResourceCollector(ABC):
15
+ """Abstract base class for AWS resource collectors.
16
+
17
+ Each AWS service (EC2, IAM, Lambda, etc.) should implement this interface
18
+ to provide a consistent way of collecting resources.
19
+ """
20
+
21
+ def __init__(self, session: boto3.Session, region: str):
22
+ """Initialize the collector.
23
+
24
+ Args:
25
+ session: Boto3 session with AWS credentials
26
+ region: AWS region to collect from (may be ignored for global services)
27
+ """
28
+ self.session = session
29
+ self.region = region
30
+ self.logger = logging.getLogger(f"{__name__}.{self.__class__.__name__}")
31
+
32
+ @abstractmethod
33
+ def collect(self) -> List[Resource]:
34
+ """Collect all resources for this service.
35
+
36
+ Returns:
37
+ List of Resource instances
38
+
39
+ Raises:
40
+ Exception: If collection fails (should be handled by caller)
41
+ """
42
+ pass
43
+
44
+ @property
45
+ @abstractmethod
46
+ def service_name(self) -> str:
47
+ """Return the AWS service name (e.g., 'ec2', 'iam', 'lambda')."""
48
+ pass
49
+
50
+ @property
51
+ def is_global_service(self) -> bool:
52
+ """Return True if this is a global service (like IAM).
53
+
54
+ Global services should only be collected once, not per-region.
55
+ """
56
+ return False
57
+
58
+ def _create_client(self, service_name: Optional[str] = None): # type: ignore
59
+ """Create a boto3 client for this service.
60
+
61
+ Args:
62
+ service_name: Service name override (defaults to self.service_name)
63
+
64
+ Returns:
65
+ Boto3 client instance
66
+ """
67
+ from ...aws.client import create_boto_client
68
+
69
+ svc = service_name or self.service_name
70
+ profile = self.session.profile_name if hasattr(self.session, "profile_name") else None
71
+
72
+ return create_boto_client(service_name=svc, region_name=self.region, profile_name=profile)
73
+
74
+ def _get_account_id(self) -> str:
75
+ """Get the AWS account ID from the session.
76
+
77
+ Returns:
78
+ 12-digit AWS account ID
79
+ """
80
+ sts = self._create_client("sts")
81
+ return sts.get_caller_identity()["Account"]
@@ -0,0 +1,55 @@
1
+ """CloudFormation resource collector."""
2
+
3
+ from typing import List
4
+
5
+ from ...models.resource import Resource
6
+ from ...utils.hash import compute_config_hash
7
+ from .base import BaseResourceCollector
8
+
9
+
10
+ class CloudFormationCollector(BaseResourceCollector):
11
+ """Collector for AWS CloudFormation resources (stacks)."""
12
+
13
+ @property
14
+ def service_name(self) -> str:
15
+ return "cloudformation"
16
+
17
+ def collect(self) -> List[Resource]:
18
+ """Collect CloudFormation resources.
19
+
20
+ Returns:
21
+ List of CloudFormation stacks
22
+ """
23
+ resources = []
24
+ client = self._create_client()
25
+
26
+ try:
27
+ paginator = client.get_paginator("describe_stacks")
28
+ for page in paginator.paginate():
29
+ for stack in page.get("Stacks", []):
30
+ stack_name = stack["StackName"]
31
+ stack_id = stack["StackId"]
32
+
33
+ # Extract tags
34
+ tags = {}
35
+ for tag in stack.get("Tags", []):
36
+ tags[tag["Key"]] = tag["Value"]
37
+
38
+ # Create resource
39
+ resource = Resource(
40
+ arn=stack_id, # Stack ID is actually the ARN
41
+ resource_type="AWS::CloudFormation::Stack",
42
+ name=stack_name,
43
+ region=self.region,
44
+ tags=tags,
45
+ config_hash=compute_config_hash(stack),
46
+ created_at=stack.get("CreationTime"),
47
+ raw_config=stack,
48
+ )
49
+ resources.append(resource)
50
+
51
+ except Exception as e:
52
+ self.logger.error(f"Error collecting CloudFormation stacks in {self.region}: {e}")
53
+
54
+ self.logger.debug(f"Collected {len(resources)} CloudFormation stacks in {self.region}")
55
+ return resources
@@ -0,0 +1,109 @@
1
+ """CloudWatch resource collector."""
2
+
3
+ from typing import List
4
+
5
+ from ...models.resource import Resource
6
+ from ...utils.hash import compute_config_hash
7
+ from .base import BaseResourceCollector
8
+
9
+
10
+ class CloudWatchCollector(BaseResourceCollector):
11
+ """Collector for AWS CloudWatch resources (alarms, log groups)."""
12
+
13
+ @property
14
+ def service_name(self) -> str:
15
+ return "cloudwatch"
16
+
17
+ def collect(self) -> List[Resource]:
18
+ """Collect CloudWatch resources.
19
+
20
+ Returns:
21
+ List of CloudWatch resources
22
+ """
23
+ resources = []
24
+ account_id = self._get_account_id()
25
+
26
+ # Collect alarms
27
+ resources.extend(self._collect_alarms(account_id))
28
+
29
+ # Collect log groups
30
+ resources.extend(self._collect_log_groups(account_id))
31
+
32
+ self.logger.debug(f"Collected {len(resources)} CloudWatch resources in {self.region}")
33
+ return resources
34
+
35
+ def _collect_alarms(self, account_id: str) -> List[Resource]:
36
+ """Collect CloudWatch alarms."""
37
+ resources = []
38
+ client = self._create_client()
39
+
40
+ try:
41
+ paginator = client.get_paginator("describe_alarms")
42
+ for page in paginator.paginate():
43
+ for alarm in page.get("MetricAlarms", []) + page.get("CompositeAlarms", []):
44
+ alarm_name = alarm["AlarmName"]
45
+ alarm_arn = alarm["AlarmArn"]
46
+
47
+ # Extract alarm type
48
+ alarm_type = "AWS::CloudWatch::Alarm"
49
+ if "CompositeAlarms" in str(type(alarm)):
50
+ alarm_type = "AWS::CloudWatch::CompositeAlarm"
51
+
52
+ # Create resource (CloudWatch alarms don't support tags directly)
53
+ resource = Resource(
54
+ arn=alarm_arn,
55
+ resource_type=alarm_type,
56
+ name=alarm_name,
57
+ region=self.region,
58
+ tags={},
59
+ config_hash=compute_config_hash(alarm),
60
+ created_at=alarm.get("AlarmConfigurationUpdatedTimestamp"),
61
+ raw_config=alarm,
62
+ )
63
+ resources.append(resource)
64
+
65
+ except Exception as e:
66
+ self.logger.error(f"Error collecting CloudWatch alarms in {self.region}: {e}")
67
+
68
+ return resources
69
+
70
+ def _collect_log_groups(self, account_id: str) -> List[Resource]:
71
+ """Collect CloudWatch log groups."""
72
+ resources = []
73
+
74
+ try:
75
+ logs_client = self._create_client("logs")
76
+
77
+ paginator = logs_client.get_paginator("describe_log_groups")
78
+ for page in paginator.paginate():
79
+ for log_group in page.get("logGroups", []):
80
+ log_group_name = log_group["logGroupName"]
81
+
82
+ # Build ARN
83
+ arn = log_group.get("arn", f"arn:aws:logs:{self.region}:{account_id}:log-group:{log_group_name}")
84
+
85
+ # Get tags
86
+ tags = {}
87
+ try:
88
+ tag_response = logs_client.list_tags_log_group(logGroupName=log_group_name)
89
+ tags = tag_response.get("tags", {})
90
+ except Exception as e:
91
+ self.logger.debug(f"Could not get tags for log group {log_group_name}: {e}")
92
+
93
+ # Create resource
94
+ resource = Resource(
95
+ arn=arn,
96
+ resource_type="AWS::Logs::LogGroup",
97
+ name=log_group_name,
98
+ region=self.region,
99
+ tags=tags,
100
+ config_hash=compute_config_hash(log_group),
101
+ created_at=None, # Log groups don't expose creation timestamp easily
102
+ raw_config=log_group,
103
+ )
104
+ resources.append(resource)
105
+
106
+ except Exception as e:
107
+ self.logger.error(f"Error collecting CloudWatch log groups in {self.region}: {e}")
108
+
109
+ return resources
@@ -0,0 +1,69 @@
1
+ """CodeBuild resource collector."""
2
+
3
+ from typing import List
4
+
5
+ from ...models.resource import Resource
6
+ from ...utils.hash import compute_config_hash
7
+ from .base import BaseResourceCollector
8
+
9
+
10
+ class CodeBuildCollector(BaseResourceCollector):
11
+ """Collector for AWS CodeBuild resources."""
12
+
13
+ @property
14
+ def service_name(self) -> str:
15
+ return "codebuild"
16
+
17
+ def collect(self) -> List[Resource]:
18
+ """Collect CodeBuild projects.
19
+
20
+ Returns:
21
+ List of CodeBuild project resources
22
+ """
23
+ resources = []
24
+ client = self._create_client()
25
+
26
+ try:
27
+ paginator = client.get_paginator("list_projects")
28
+ project_names = []
29
+ for page in paginator.paginate():
30
+ project_names.extend(page.get("projects", []))
31
+
32
+ # Get detailed info for projects (in batches of 100)
33
+ for i in range(0, len(project_names), 100):
34
+ batch = project_names[i : i + 100]
35
+ try:
36
+ response = client.batch_get_projects(names=batch)
37
+ for project in response.get("projects", []):
38
+ project_name = project["name"]
39
+ project_arn = project["arn"]
40
+
41
+ # Extract tags
42
+ tags = {}
43
+ for tag in project.get("tags", []):
44
+ tags[tag["key"]] = tag["value"]
45
+
46
+ # Extract creation date
47
+ created_at = project.get("created")
48
+
49
+ # Create resource
50
+ resource = Resource(
51
+ arn=project_arn,
52
+ resource_type="AWS::CodeBuild::Project",
53
+ name=project_name,
54
+ region=self.region,
55
+ tags=tags,
56
+ config_hash=compute_config_hash(project),
57
+ created_at=created_at,
58
+ raw_config=project,
59
+ )
60
+ resources.append(resource)
61
+
62
+ except Exception as e:
63
+ self.logger.debug(f"Error processing project batch: {e}")
64
+
65
+ except Exception as e:
66
+ self.logger.error(f"Error collecting CodeBuild projects in {self.region}: {e}")
67
+
68
+ self.logger.debug(f"Collected {len(resources)} CodeBuild projects in {self.region}")
69
+ return resources
@@ -0,0 +1,82 @@
1
+ """CodePipeline resource collector."""
2
+
3
+ from typing import List
4
+
5
+ from ...models.resource import Resource
6
+ from ...utils.hash import compute_config_hash
7
+ from .base import BaseResourceCollector
8
+
9
+
10
+ class CodePipelineCollector(BaseResourceCollector):
11
+ """Collector for AWS CodePipeline resources."""
12
+
13
+ @property
14
+ def service_name(self) -> str:
15
+ return "codepipeline"
16
+
17
+ def collect(self) -> List[Resource]:
18
+ """Collect CodePipeline pipelines.
19
+
20
+ Returns:
21
+ List of CodePipeline resources
22
+ """
23
+ resources = []
24
+ client = self._create_client()
25
+
26
+ try:
27
+ paginator = client.get_paginator("list_pipelines")
28
+ for page in paginator.paginate():
29
+ for pipeline_summary in page.get("pipelines", []):
30
+ pipeline_name = pipeline_summary["name"]
31
+
32
+ try:
33
+ # Get detailed pipeline info
34
+ pipeline_response = client.get_pipeline(name=pipeline_name)
35
+ pipeline = pipeline_response.get("pipeline", {})
36
+ metadata = pipeline_response.get("metadata", {})
37
+
38
+ # Get tags
39
+ tags = {}
40
+ try:
41
+ # Build ARN for tags
42
+ pipeline_arn = metadata.get("pipelineArn", "")
43
+ if pipeline_arn:
44
+ tag_response = client.list_tags_for_resource(resourceArn=pipeline_arn)
45
+ for tag in tag_response.get("tags", []):
46
+ tags[tag["key"]] = tag["value"]
47
+ except Exception as e:
48
+ self.logger.debug(f"Could not get tags for pipeline {pipeline_name}: {e}")
49
+
50
+ # Extract creation/update dates
51
+ created_at = metadata.get("created")
52
+
53
+ # Build ARN
54
+ arn = metadata.get(
55
+ "pipelineArn", f"arn:aws:codepipeline:{self.region}:*:pipeline/{pipeline_name}"
56
+ )
57
+
58
+ # Create resource (exclude large stage/action definitions for size)
59
+ config = {k: v for k, v in pipeline.items() if k not in ["stages"]}
60
+ config["stageCount"] = len(pipeline.get("stages", []))
61
+
62
+ resource = Resource(
63
+ arn=arn,
64
+ resource_type="AWS::CodePipeline::Pipeline",
65
+ name=pipeline_name,
66
+ region=self.region,
67
+ tags=tags,
68
+ config_hash=compute_config_hash(config),
69
+ created_at=created_at,
70
+ raw_config=config,
71
+ )
72
+ resources.append(resource)
73
+
74
+ except Exception as e:
75
+ self.logger.debug(f"Error processing pipeline {pipeline_name}: {e}")
76
+ continue
77
+
78
+ except Exception as e:
79
+ self.logger.error(f"Error collecting CodePipeline pipelines in {self.region}: {e}")
80
+
81
+ self.logger.debug(f"Collected {len(resources)} CodePipeline pipelines in {self.region}")
82
+ return resources
@@ -0,0 +1,65 @@
1
+ """DynamoDB resource collector."""
2
+
3
+ from typing import List
4
+
5
+ from ...models.resource import Resource
6
+ from ...utils.hash import compute_config_hash
7
+ from .base import BaseResourceCollector
8
+
9
+
10
+ class DynamoDBCollector(BaseResourceCollector):
11
+ """Collector for AWS DynamoDB resources (tables)."""
12
+
13
+ @property
14
+ def service_name(self) -> str:
15
+ return "dynamodb"
16
+
17
+ def collect(self) -> List[Resource]:
18
+ """Collect DynamoDB resources.
19
+
20
+ Returns:
21
+ List of DynamoDB tables
22
+ """
23
+ resources = []
24
+ client = self._create_client()
25
+
26
+ try:
27
+ paginator = client.get_paginator("list_tables")
28
+ for page in paginator.paginate():
29
+ for table_name in page.get("TableNames", []):
30
+ try:
31
+ # Get table details
32
+ table_response = client.describe_table(TableName=table_name)
33
+ table = table_response.get("Table", {})
34
+
35
+ table_arn = table.get("TableArn")
36
+
37
+ # Get tags
38
+ tags = {}
39
+ try:
40
+ tag_response = client.list_tags_of_resource(ResourceArn=table_arn)
41
+ tags = {tag["Key"]: tag["Value"] for tag in tag_response.get("Tags", [])}
42
+ except Exception as e:
43
+ self.logger.debug(f"Could not get tags for DynamoDB table {table_name}: {e}")
44
+
45
+ # Create resource
46
+ resource = Resource(
47
+ arn=table_arn,
48
+ resource_type="AWS::DynamoDB::Table",
49
+ name=table_name,
50
+ region=self.region,
51
+ tags=tags,
52
+ config_hash=compute_config_hash(table),
53
+ created_at=table.get("CreationDateTime"),
54
+ raw_config=table,
55
+ )
56
+ resources.append(resource)
57
+
58
+ except Exception as e:
59
+ self.logger.debug(f"Could not get details for DynamoDB table {table_name}: {e}")
60
+
61
+ except Exception as e:
62
+ self.logger.error(f"Error collecting DynamoDB tables in {self.region}: {e}")
63
+
64
+ self.logger.debug(f"Collected {len(resources)} DynamoDB tables in {self.region}")
65
+ return resources