aws_advanced_python_wrapper/utils/rdsutils.py (189 lines of code) (raw):

# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"). # You may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from __future__ import annotations from re import Match, search, sub from typing import Dict, Optional from aws_advanced_python_wrapper.utils.rds_url_type import RdsUrlType class RdsUtils: """ Aurora DB clusters support different endpoints. More details about Aurora RDS endpoints can be found at https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/Aurora.Overview.Endpoints.html Details how to use RDS Proxy endpoints can be found at https://docs.aws.amazon.com/AmazonRDS/latest/AuroraUserGuide/rds-proxy-endpoints.html Values like "<...>" depend on particular Aurora cluster. For example: "<database-cluster-name>" Cluster (Writer) Endpoint: <database-cluster-name>.cluster-<xyz>.<aws-region>.rds.amazonaws.com Example: test-postgres.cluster-123456789012.us-east-2.rds.amazonaws.com Cluster Reader Endpoint: <database-cluster-name>.cluster-ro-<xyz>.<aws-region>.rds.amazonaws.com Example: test-postgres.cluster-ro-123456789012.us-east-2.rds.amazonaws.com Cluster Custom Endpoint: <cluster-name-alias>.cluster-custom-<xyz>.<aws-region>.rds.amazonaws.com Example: test-postgres-alias.cluster-custom-123456789012.us-east-2.rds.amazonaws.com Instance Endpoint: <instance-name>.<xyz>.<aws-region>.rds.amazonaws.com Example: test-postgres-instance-1.123456789012.us-east-2.rds.amazonaws.com Similar endpoints for China regions have different structure and are presented below. Cluster (Writer) Endpoint: <database-cluster-name>.cluster-<xyz>.rds.<aws-region>.amazonaws.com.cn Example: test-postgres.cluster-123456789012.rds.cn-northwest-1.amazonaws.com.cn Cluster Reader Endpoint: <database-cluster-name>.cluster-ro-<xyz>.rds.<aws-region>.amazonaws.com.cn Example: test-postgres.cluster-ro-123456789012.rds.cn-northwest-1.amazonaws.com.cn Cluster Custom Endpoint: <cluster-name-alias>.cluster-custom-<xyz>.rds.<aws-region>.amazonaws.com.cn Example: test-postgres-alias.cluster-custom-123456789012.rds.cn-northwest-1.amazonaws.com.cn Instance Endpoint: <instance-name>.<xyz>.rds.<aws-region>.amazonaws.com.cn Example: test-postgres-instance-1.123456789012.rds.cn-northwest-1.amazonaws.com.cn """ AURORA_DNS_PATTERN = r"^(?P<instance>.+)\." \ r"(?P<dns>proxy-|cluster-|cluster-ro-|cluster-custom-|limitless-)?" \ r"(?P<domain>[a-zA-Z0-9]+\." \ r"(?P<region>[a-zA-Z0-9\-]+)\.rds\.amazonaws\.com)(?!\.cn)$" AURORA_INSTANCE_PATTERN = r"^(?P<instance>.+)\." \ r"(?P<domain>[a-zA-Z0-9]+\." \ r"(?P<region>[a-zA-Z0-9\-]+)\.rds\.amazonaws\.com)(?!\.cn)$" AURORA_CLUSTER_PATTERN = r"^(?P<instance>.+)\." \ r"(?P<dns>cluster-|cluster-ro-)+" \ r"(?P<domain>[a-zA-Z0-9]+\." \ r"(?P<region>[a-zA-Z0-9\-]+)\.rds\.amazonaws\.com)(?!\.cn)$" AURORA_CUSTOM_CLUSTER_PATTERN = r"^(?P<instance>.+)\." \ r"(?P<dns>cluster-custom-)+" \ r"(?P<domain>[a-zA-Z0-9]+\." \ r"(?P<region>[a-zA-Z0-9\-]+)\.rds\.amazonaws\.com)(?!\.cn)$" AURORA_PROXY_DNS_PATTERN = r"^(?P<instance>.+)\." \ r"(?P<dns>proxy-)+" \ r"(?P<domain>[a-zA-Z0-9]+\." \ r"(?P<region>[a-zA-Z0-9\\-]+)\.rds\.amazonaws\.com)(?!\.cn)$" AURORA_OLD_CHINA_DNS_PATTERN = r"^(?P<instance>.+)\." \ r"(?P<dns>proxy-|cluster-|cluster-ro-|cluster-custom-|limitless-)?" \ r"(?P<domain>[a-zA-Z0-9]+\." \ r"(?P<region>[a-zA-Z0-9\-]+)\.rds\.amazonaws\.com\.cn)$" AURORA_CHINA_DNS_PATTERN = r"^(?P<instance>.+)\." \ r"(?P<dns>proxy-|cluster-|cluster-ro-|cluster-custom-|limitless-)?" \ r"(?P<domain>[a-zA-Z0-9]+\." \ r"rds\.(?P<region>[a-zA-Z0-9\-]+)\.amazonaws\.com\.cn)$" AURORA_OLD_CHINA_CLUSTER_PATTERN = r"^(?P<instance>.+)\." \ r"(?P<dns>cluster-|cluster-ro-)+" \ r"(?P<domain>[a-zA-Z0-9]+\." \ r"(?P<region>[a-zA-Z0-9\-]+)\.rds\.amazonaws\.com\.cn)$" AURORA_CHINA_CLUSTER_PATTERN = r"^(?P<instance>.+)\." \ r"(?P<dns>cluster-|cluster-ro-)+" \ r"(?P<domain>[a-zA-Z0-9]+\." \ r"rds\.(?P<region>[a-zA-Z0-9\-]+)\.amazonaws\.com\.cn)$" AURORA_GOV_DNS_PATTERN = r"^(?P<instance>.+)\." \ r"(?P<dns>proxy-|cluster-|cluster-ro-|cluster-custom-|limitless-)?" \ r"(?P<domain>[a-zA-Z0-9]+\.rds\.(?P<region>[a-zA-Z0-9\-]+)" \ r"\.(amazonaws\.com|c2s\.ic\.gov|sc2s\.sgov\.gov))$" AURORA_GOV_CLUSTER_PATTERN = r"^(?P<instance>.+)\." \ r"(?P<dns>cluster-|cluster-ro-)+" \ r"(?P<domain>[a-zA-Z0-9]+\.rds\.(?P<region>[a-zA-Z0-9\-]+)" \ r"\.(amazonaws\.com|c2s\.ic\.gov|sc2s\.sgov\.gov))$" ELB_PATTERN = r"^(?<instance>.+)\.elb\.((?<region>[a-zA-Z0-9\-]+)\.amazonaws\.com)$" IP_V4 = r"^(([1-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){1}" \ r"(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){2}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])" IP_V6 = r"^[0-9a-fA-F]{1,4}(:[0-9a-fA-F]{1,4}){7}" IP_V6_COMPRESSED = r"^(([0-9A-Fa-f]{1,4}(:[0-9A-Fa-f]{1,4}){0,5})?)::(([0-9A-Fa-f]{1,4}(:[0-9A-Fa-f]{1,4}){0,5})?)" DNS_GROUP = "dns" DOMAIN_GROUP = "domain" INSTANCE_GROUP = "instance" REGION_GROUP = "region" CACHE_DNS_PATTERNS: Dict[str, Match[str]] = {} CACHE_PATTERNS: Dict[str, str] = {} def is_rds_cluster_dns(self, host: str) -> bool: dns_group = self._get_dns_group(host) return dns_group is not None and dns_group.casefold() in ["cluster-", "cluster-ro-"] def is_rds_custom_cluster_dns(self, host: str) -> bool: dns_group = self._get_dns_group(host) return dns_group is not None and dns_group.casefold() == "cluster-custom-" def is_rds_dns(self, host: str) -> bool: if not host or not host.strip(): return False pattern = self._find(host, [RdsUtils.AURORA_DNS_PATTERN, RdsUtils.AURORA_CHINA_DNS_PATTERN, RdsUtils.AURORA_OLD_CHINA_DNS_PATTERN, RdsUtils.AURORA_GOV_DNS_PATTERN]) group = self._get_regex_group(pattern, RdsUtils.DNS_GROUP) if group: RdsUtils.CACHE_PATTERNS[host] = group return pattern is not None def is_rds_instance(self, host: str) -> bool: return self._get_dns_group(host) is None and self.is_rds_dns(host) def is_rds_proxy_dns(self, host: str) -> bool: dns_group = self._get_dns_group(host) return dns_group is not None and dns_group.casefold() == "proxy-" def get_rds_instance_host_pattern(self, host: str) -> str: if not host or not host.strip(): return "?" match = self._get_group(host, RdsUtils.DOMAIN_GROUP) if match: return f"?.{match}" return "?" def get_rds_region(self, host: Optional[str]): if not host or not host.strip(): return None group = self._get_group(host, RdsUtils.REGION_GROUP) if group: return group elb_matcher = search(RdsUtils.ELB_PATTERN, host) if elb_matcher: return elb_matcher.group(RdsUtils.REGION_GROUP) return None def is_writer_cluster_dns(self, host: str) -> bool: dns_group = self._get_dns_group(host) return dns_group is not None and dns_group.casefold() == "cluster-" def is_reader_cluster_dns(self, host: str) -> bool: dns_group = self._get_dns_group(host) return dns_group is not None and dns_group.casefold() == "cluster-ro-" def get_rds_cluster_host_url(self, host: str): if not host or not host.strip(): return None for pattern in [RdsUtils.AURORA_DNS_PATTERN, RdsUtils.AURORA_CHINA_DNS_PATTERN, RdsUtils.AURORA_OLD_CHINA_DNS_PATTERN, RdsUtils.AURORA_GOV_DNS_PATTERN]: if m := search(pattern, host): group = self._get_regex_group(m, RdsUtils.DNS_GROUP) if group is not None: return sub(pattern, r"\g<instance>.cluster-\g<domain>", host) return None return None def get_cluster_id(self, host: str) -> Optional[str]: if host is None or not host.strip(): return None if self._get_dns_group(host) is not None: return self._get_group(host, self.INSTANCE_GROUP) return None def get_instance_id(self, host: str) -> Optional[str]: if self._get_dns_group(host) is None: return self._get_group(host, self.INSTANCE_GROUP) return None def is_ipv4(self, host: str) -> bool: if host is None or not host.strip(): return False return search(RdsUtils.IP_V4, host) is not None def is_ipv6(self, host: str) -> bool: if host is None or not host.strip(): return False return search(RdsUtils.IP_V6_COMPRESSED, host) is not None or search(RdsUtils.IP_V6, host) is not None def is_dns_pattern_valid(self, host: str) -> bool: return "?" in host def identify_rds_type(self, host: Optional[str]) -> RdsUrlType: if host is None or not host.strip(): return RdsUrlType.OTHER if self.is_ipv4(host) or self.is_ipv6(host): return RdsUrlType.IP_ADDRESS elif self.is_writer_cluster_dns(host): return RdsUrlType.RDS_WRITER_CLUSTER elif self.is_reader_cluster_dns(host): return RdsUrlType.RDS_READER_CLUSTER elif self.is_rds_custom_cluster_dns(host): return RdsUrlType.RDS_CUSTOM_CLUSTER elif self.is_rds_proxy_dns(host): return RdsUrlType.RDS_PROXY elif self.is_rds_instance(host): return RdsUrlType.RDS_INSTANCE return RdsUrlType.OTHER def _find(self, host: str, patterns: list): if not host or not host.strip(): return None for pattern in patterns: match = RdsUtils.CACHE_DNS_PATTERNS.get(host) if match: return match match = search(pattern, host) if match: RdsUtils.CACHE_DNS_PATTERNS[host] = match return match return None def _get_regex_group(self, pattern: Match[str], group_name: str): if pattern is None: return None return pattern.group(group_name) def _get_group(self, host: str, group: str): if not host or not host.strip(): return None pattern = self._find(host, [RdsUtils.AURORA_DNS_PATTERN, RdsUtils.AURORA_CHINA_DNS_PATTERN, RdsUtils.AURORA_OLD_CHINA_DNS_PATTERN, RdsUtils.AURORA_GOV_DNS_PATTERN]) return self._get_regex_group(pattern, group) def _get_dns_group(self, host: str): return self._get_group(host, RdsUtils.DNS_GROUP) def remove_port(self, url: str): if not url or not url.strip(): return None if ":" in url: return url.split(":")[0] return url @staticmethod def clear_cache(): RdsUtils.CACHE_PATTERNS.clear() RdsUtils.CACHE_DNS_PATTERNS.clear()