from typing import Optional, Any, Dict, List, Union from phi.aws.api_client import AwsApiClient from phi.aws.resource.base import AwsResource from phi.aws.resource.ec2.subnet import Subnet from phi.aws.resource.ec2.security_group import SecurityGroup from phi.cli.console import print_info from phi.utils.log import logger class LoadBalancer(AwsResource): """ Reference: - https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/elbv2.html """ resource_type: Optional[str] = "LoadBalancer" service_name: str = "elbv2" # Name of the Load Balancer. name: str subnets: Optional[List[Union[str, Subnet]]] = None subnet_mappings: Optional[List[Dict[str, str]]] = None security_groups: Optional[List[Union[str, SecurityGroup]]] = None scheme: Optional[str] = None tags: Optional[List[Dict[str, str]]] = None type: Optional[str] = None ip_address_type: Optional[str] = None customer_owned_ipv_4_pool: Optional[str] = None # Protocol for load_balancer: HTTP or HTTPS protocol: str = "HTTP" def _create(self, aws_client: AwsApiClient) -> bool: """Creates the Load Balancer Args: aws_client: The AwsApiClient for the current Load Balancer """ print_info(f"Creating {self.get_resource_type()}: {self.get_resource_name()}") # create a dict of args which are not null, otherwise aws type validation fails not_null_args: Dict[str, Any] = {} if self.subnets is not None: subnet_ids = [] for subnet in self.subnets: if isinstance(subnet, Subnet): subnet_ids.append(subnet.name) elif isinstance(subnet, str): subnet_ids.append(subnet) not_null_args["Subnets"] = subnet_ids if self.subnet_mappings is not None: not_null_args["SubnetMappings"] = self.subnet_mappings if self.security_groups is not None: security_group_ids = [] for sg in self.security_groups: if isinstance(sg, SecurityGroup): security_group_ids.append(sg.get_security_group_id(aws_client)) else: security_group_ids.append(sg) not_null_args["SecurityGroups"] = security_group_ids if self.scheme is not None: not_null_args["Scheme"] = self.scheme if self.tags is not None: not_null_args["tags"] = self.tags if self.type is not None: not_null_args["Type"] = self.type if self.ip_address_type is not None: not_null_args["IpAddressType"] = self.ip_address_type if self.customer_owned_ipv_4_pool is not None: not_null_args["CustomerOwnedIpv4Pool"] = self.customer_owned_ipv_4_pool # Create LoadBalancer service_client = self.get_service_client(aws_client) try: create_response = service_client.create_load_balancer( Name=self.name, **not_null_args, ) logger.debug(f"Create Response: {create_response}") resource_dict = create_response.get("LoadBalancers", {}) # Validate resource creation if resource_dict is not None: self.active_resource = create_response return True except Exception as e: logger.error(f"{self.get_resource_type()} could not be created.") logger.error(e) return False def post_create(self, aws_client: AwsApiClient) -> bool: # Wait for LoadBalancer to be created if self.wait_for_create: try: print_info(f"Waiting for {self.get_resource_type()} to be created.") waiter = self.get_service_client(aws_client).get_waiter("load_balancer_exists") waiter.wait( Names=[self.get_resource_name()], WaiterConfig={ "Delay": self.waiter_delay, "MaxAttempts": self.waiter_max_attempts, }, ) except Exception as e: logger.error("Waiter failed.") logger.error(e) # Read the LoadBalancer elb = self._read(aws_client) if elb is None: logger.error(f"Error reading {self.get_resource_type()}. Please get DNS name manually.") else: dns_name = elb.get("DNSName", None) print_info(f"LoadBalancer DNS: {self.protocol.lower()}://{dns_name}") return True def _read(self, aws_client: AwsApiClient) -> Optional[Any]: """Returns the LoadBalancer Args: aws_client: The AwsApiClient for the current LoadBalancer """ logger.debug(f"Reading {self.get_resource_type()}: {self.get_resource_name()}") from botocore.exceptions import ClientError service_client = self.get_service_client(aws_client) try: describe_response = service_client.describe_load_balancers(Names=[self.name]) logger.debug(f"Describe Response: {describe_response}") resource_list = describe_response.get("LoadBalancers", None) if resource_list is not None and isinstance(resource_list, list): self.active_resource = resource_list[0] except ClientError as ce: logger.debug(f"ClientError: {ce}") except Exception as e: logger.error(f"Error reading {self.get_resource_type()}.") logger.error(e) return self.active_resource def _delete(self, aws_client: AwsApiClient) -> bool: """Deletes the LoadBalancer Args: aws_client: The AwsApiClient for the current LoadBalancer """ print_info(f"Deleting {self.get_resource_type()}: {self.get_resource_name()}") service_client = self.get_service_client(aws_client) self.active_resource = None try: lb_arn = self.get_arn(aws_client) if lb_arn is None: logger.warning(f"{self.get_resource_type()} not found.") return True delete_response = service_client.delete_load_balancer(LoadBalancerArn=lb_arn) logger.debug(f"Delete Response: {delete_response}") return True except Exception as e: logger.error(f"{self.get_resource_type()} could not be deleted.") logger.error("Please try again or delete resources manually.") logger.error(e) return False def post_delete(self, aws_client: AwsApiClient) -> bool: # Wait for LoadBalancer to be deleted if self.wait_for_delete: try: print_info(f"Waiting for {self.get_resource_type()} to be deleted.") waiter = self.get_service_client(aws_client).get_waiter("load_balancers_deleted") waiter.wait( Names=[self.get_resource_name()], WaiterConfig={ "Delay": self.waiter_delay, "MaxAttempts": self.waiter_max_attempts, }, ) except Exception as e: logger.error("Waiter failed.") logger.error(e) return True def get_arn(self, aws_client: AwsApiClient) -> Optional[str]: lb = self._read(aws_client) if lb is None: return None lb_arn = lb.get("LoadBalancerArn", None) return lb_arn