Ejemplo n.º 1
0
    def _list_from_aws_result_to_resources(
        cls: Type["AWSResourceSpec"],
        list_from_aws_result: ListFromAWSResult,
        context: Dict[str, str],
    ) -> List[Resource]:
        resources: List[Resource] = []
        for arn, resource_dict in list_from_aws_result.resources.items():
            try:
                partial_resource_link_collection = cls.schema.parse(
                    resource_dict, context)
            except Exception as ex:
                raise SchemaParseException((
                    f"Error parsing {cls.__name__} : "
                    f"{arn}:\n\nData: {resource_dict}\n\nError: {ex}")) from ex
            resource_account_id = arn.split(":")[4]
            account_region_links = []
            if resource_account_id:
                if resource_account_id != "aws":
                    account_link = ResourceLink(
                        pred="account",
                        obj=f"arn:aws::::account/{resource_account_id}")
                    account_region_links.append(account_link)
                    resource_region_name = arn.split(":")[3]
                    if resource_region_name:
                        region_link = ResourceLink(
                            pred="region",
                            obj=
                            f"arn:aws:::{resource_account_id}:region/{resource_region_name}",
                        )
                        account_region_links.append(region_link)
            link_collection = partial_resource_link_collection + LinkCollection.from_links(
                account_region_links)

            resource = Resource(
                resource_id=arn,
                type=cls.get_full_type_name(),
                link_collection=link_collection,
            )
            resources.append(resource)
        return resources
    def merge_resources(resource_id: str,
                        resources: List[Resource]) -> Resource:
        """Merge multiple resources with the same id into one.  This is permissible
        in two cases:

            1) If all resources have the same value for 'get_full_type' and no key/values
            conflict they will be merged into a single resource by combining all key/values
            into a single resource

            2) If all resources do not have the same value for 'get_full_type', if classes
            have the allow_clobber attribute set depending on the values a resource may be
            created.

        Args:
             resource_id: common resource id
             resources: list of Resources to merge

        Returns:
            merged Resource object

        Raises:
            UnmergableDuplicateResourceIdsFoundException if resources could not be merged.
        """
        full_type_names = {resource.type for resource in resources}
        if len(full_type_names) > 1:
            # in this case conflicting resources have different full_type_names, this
            # can be a permissible if allow_clobber is used. Here we determine this by
            # building a dict where the keys are resource spec classes which have
            # appeared in the duplicate resources allow_clobber list and the
            # values are the classes where they appeared.  If at the end in
            # this dict there are any values which match the complete list of
            # spec classes, we use the resource of that type
            spec_classes_resources: DefaultDict[
                Type[ResourceSpec], List[Resource]] = defaultdict(list)
            allow_clobber_classes_classes: DefaultDict[
                Type[ResourceSpec], Set[Type[ResourceSpec]]] = defaultdict(set)
            for resource in resources:
                full_type_name = resource.type
                spec_classes: List[
                    Type[ResourceSpec]] = ResourceSpec.get_by_full_type_name(
                        full_type_name)
                for spec_class in spec_classes:
                    spec_classes_resources[spec_class].append(resource)
                    for allow_clobber_class in spec_class.allow_clobber:
                        allow_clobber_classes_classes[allow_clobber_class].add(
                            spec_class)
            all_spec_classes = set(spec_classes_resources.keys())
            winning_class = None
            for allow_clobber_class, classes in allow_clobber_classes_classes.items(
            ):
                if len(classes) == len(all_spec_classes) - 1:
                    winning_class = (all_spec_classes - classes).pop()
            if not winning_class:
                raise UnmergableDuplicateResourceIdsFoundException(
                    (f"Multiple resources for {resource_id} with "
                     f"different types that aren't clobberable: "
                     f"{[resource.dict() for resource in resources]}"))
            resources = spec_classes_resources[winning_class]
        full_type_names = {resource.type for resource in resources}
        merged_resource_type_name = full_type_names.pop()
        merged_link_keys_links: Dict[str, Link] = {}
        for duplicate_resource in resources:
            for link in duplicate_resource.link_collection.get_links():
                duplicate_link = merged_link_keys_links.get(link.pred)
                if duplicate_link:
                    if type(duplicate_link) != type(link):
                        raise UnmergableDuplicateResourceIdsFoundException(
                            f"Conflicting link types {type(link)}, {type(duplicate_link)} found in duplicate #s {resources}"
                        )
                    if duplicate_link.obj != link.obj:
                        raise UnmergableDuplicateResourceIdsFoundException(
                            f"Conflicting link values {link.obj}, {duplicate_link.obj} found in duplicate #s {resources}"
                        )
                else:
                    merged_link_keys_links[link.pred] = link
        merged_links = list(merged_link_keys_links.values())
        link_collection = LinkCollection.from_links(merged_links)

        return Resource(
            resource_id=resource_id,
            type=merged_resource_type_name,
            link_collection=link_collection,
        )