def dedupe_resources(resources: Iterable[Resource]) -> Tuple[Resource, ...]: """Resolve any duplicate resource ids. In general duplicate resource ids can have their Resource objects merged if they are of the same type and all fields are identical or additive only across the resources or if one of the Resources allows a special merge via its ResourceSpec class' `allow_clobber` attribute.""" resource_ids_resources: DefaultDict[str, List[Resource]] = defaultdict(list) for resource in resources: resource_ids_resources[resource.resource_id].append(resource) merged_resources: List[Resource] = [] for resource_id, candidate_resources in resource_ids_resources.items(): if len(candidate_resources) > 1: merged_resource = ResourceSpec.merge_resources( resource_id=resource_id, resources=candidate_resources) merged_resources.append(merged_resource) for merged_resource in merged_resources: resource_ids_resources[merged_resource.resource_id] = [merged_resource] # at this point all values in resource_ids_resources should be single-value lists. # validate that and build a list of deduped_resources from those single-values deduped_resources = [] for resource_id, resource_list in resource_ids_resources.items(): if len(resource_list) != 1: raise Exception( f"More than one resource found for {resource_id}: {resource_list}" ) deduped_resources.append(resource_list[0]) return tuple(deduped_resources)
def test(self): account_id = "012345678901" errors = ["foo", "boo"] unscanned_account_resource = UnscannedAccountResourceSpec.create_resource( account_id=account_id, errors=errors) resource = ResourceSpec.merge_resources("foo", [unscanned_account_resource]) self.assertEqual(resource.resource_id, "foo") self.assertEqual(resource.type, "aws:unscanned-account") self.assertEqual(len(resource.link_collection.simple_links), 2) self.assertEqual( resource.link_collection.simple_links[0], SimpleLink(pred="account_id", obj="012345678901"), ) self.assertEqual(resource.link_collection.simple_links[1].pred, "error") self.assertTrue( resource.link_collection.simple_links[1].obj.startswith( "foo\nboo - "))
def test(self): account_id = "012345678901" errors = ["foo", "boo"] unscanned_account_resource = UnscannedAccountResourceSpec.create_resource( account_id=account_id, errors=errors) resource = ResourceSpec.merge_resources("foo", [unscanned_account_resource]) resource_dict = resource.to_dict() self.assertEqual(resource_dict["type"], "aws:unscanned-account") self.assertEqual(len(resource_dict["links"]), 2) self.assertEqual(resource_dict["links"][0], { 'pred': 'account_id', 'obj': '012345678901', 'type': 'simple' }) self.assertEqual(resource_dict["links"][1]["pred"], "error") self.assertEqual(resource_dict["links"][1]["type"], "simple") self.assertTrue( resource_dict["links"][1]["obj"].startswith("foo\nboo - "))
def _resolve_duplicates(self) -> None: """Resolve any duplicate resource ids. In general duplicate resource ids can have their Resource objects merged if they are of the same type and all fields are identical or additive only across the resources or if one of the Resources allows a special merge via its ResourceSpec class' `allow_clobber` attribute.""" resource_ids_resources: DefaultDict[str, List[Resource]] = defaultdict(list) for resource in self.resources: resource_ids_resources[resource.resource_id].append(resource) merged_resources: List[Resource] = [] for resource_id, resources in resource_ids_resources.items(): if len(resources) > 1: merged_resource = ResourceSpec.merge_resources( resource_id=resource_id, resources=resources) merged_resources.append(merged_resource) for merged_resource in merged_resources: self.resources = [ resource for resource in self.resources if resource.resource_id != merged_resource.resource_id ] self.resources.append(merged_resource)