def parse(self, data: Dict[str, Any], context: Dict[str, Any]) -> List[Link]: """Parse this field and return a list of Links. Args: data: data to parse context: contains data from higher level parsing code. Returns: List of Link objects. At most one link will be returned. """ if isinstance(self._resource_spec_class, str): resource_spec_class: Type[ ResourceSpec] = ResourceSpec.get_by_class_name( self._resource_spec_class) else: resource_spec_class = self._resource_spec_class if not self.alti_key: self.alti_key = resource_spec_class.type_name short_resource_id = data.get(self.source_key) if not short_resource_id: if self.optional: return [] raise ResourceLinkFieldSourceKeyNotFoundException( f"Expected key '{self.source_key}' with non-empty/zero value in {data}" ) if self.value_is_id: resource_id = short_resource_id else: resource_id = resource_spec_class.generate_id( short_resource_id, context) return [TransientResourceLinkLink(pred=self.alti_key, obj=resource_id)]
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 parse(self, data: str, context: Dict[str, Any]) -> List[Link]: """Parse this field and return a list of Links. Args: data: data to parse context: contains data from higher level parsing code. Returns: List of Link objects. At most one link will be returned. """ if isinstance(self._resource_spec_class, str): resource_spec_class: Type[ ResourceSpec] = ResourceSpec.get_by_class_name( self._resource_spec_class) else: resource_spec_class = self._resource_spec_class if not self.alti_key: self.alti_key = resource_spec_class.type_name short_resource_id = data if self.value_is_id: resource_id = short_resource_id else: resource_id = resource_spec_class.generate_id( short_resource_id, context) return [ResourceLinkLink(pred=self.alti_key, obj=resource_id)]
def parse(self, data: str, context: Dict[str, Any]) -> LinkCollection: """Parse this field and return a LinkCollection. Args: data: data to parse context: contains data from higher level parsing code. Returns: LinkCollection """ if isinstance(self._resource_spec_class, str): resource_spec_class: Type[ ResourceSpec] = ResourceSpec.get_by_class_name( self._resource_spec_class) else: resource_spec_class = self._resource_spec_class if not self.alti_key: self.alti_key = resource_spec_class.type_name short_resource_id = data if self.value_is_id: resource_id = short_resource_id else: resource_id = resource_spec_class.generate_id( short_resource_id, context) return LinkCollection( resource_links=[ResourceLink(pred=self.alti_key, obj=resource_id)], )
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)
def parse(self, data: Dict[str, Any], context: Dict[str, Any]) -> LinkCollection: """Parse this field and return a LinkCollection Args: data: data to parse context: contains data from higher level parsing code. Returns: LinkCollection """ if isinstance(self._resource_spec_class, str): resource_spec_class: Type[ ResourceSpec] = ResourceSpec.get_by_class_name( self._resource_spec_class) else: resource_spec_class = self._resource_spec_class if not self.alti_key: self.alti_key = resource_spec_class.type_name short_resource_id = data.get(self.source_key) if not short_resource_id: if self.optional: return LinkCollection() raise ResourceLinkFieldSourceKeyNotFoundException( f"Expected key '{self.source_key}' with non-empty/zero value in {data}" ) if not isinstance(short_resource_id, str): raise ResourceLinkFieldValueNotAStringException(( f"ResourceLinkField for {self.source_key} expected a string but got a " f"{type(short_resource_id)} : {short_resource_id}")) if self.value_is_id: resource_id = short_resource_id else: resource_id = resource_spec_class.generate_id( short_resource_id, context) return LinkCollection( resource_links=[ResourceLink(pred=self.alti_key, obj=resource_id)], )