AmmarFahmy
adding all files
105b369
from typing import Optional, Any, List
from phi.aws.api_client import AwsApiClient
from phi.aws.resource.base import AwsResource
from phi.cli.console import print_info
from phi.utils.log import logger
class CloudFormationStack(AwsResource):
"""
Reference:
- https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/cloudformation.html#service-resource
"""
resource_type: Optional[str] = "CloudFormationStack"
service_name: str = "cloudformation"
# StackName: The name must be unique in the Region in which you are creating the stack.
name: str
# Location of file containing the template body.
# The URL must point to a template (max size: 460,800 bytes) that's located in an
# Amazon S3 bucket or a Systems Manager document.
template_url: str
# parameters: Optional[List[Dict[str, Union[str, bool]]]] = None
# disable_rollback: Optional[bool] = None
def _create(self, aws_client: AwsApiClient) -> bool:
"""Creates the CloudFormationStack
Args:
aws_client: The AwsApiClient for the current cluster
"""
print_info(f"Creating {self.get_resource_type()}: {self.get_resource_name()}")
# Step 1: Create CloudFormationStack
service_resource = self.get_service_resource(aws_client)
try:
stack = service_resource.create_stack(
StackName=self.name,
TemplateURL=self.template_url,
)
logger.debug(f"Stack: {stack}")
# Validate Stack creation
stack.load()
creation_time = stack.creation_time
logger.debug(f"creation_time: {creation_time}")
if creation_time is not None:
self.active_resource = stack
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 Stack 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("stack_create_complete")
waiter.wait(
StackName=self.name,
WaiterConfig={
"Delay": self.waiter_delay,
"MaxAttempts": self.waiter_max_attempts,
},
)
except Exception as e:
logger.error("Waiter failed.")
logger.error(e)
return False
return True
def _read(self, aws_client: AwsApiClient) -> Optional[Any]:
"""Returns the CloudFormationStack
Args:
aws_client: The AwsApiClient for the current cluster
"""
logger.debug(f"Reading {self.get_resource_type()}: {self.get_resource_name()}")
from botocore.exceptions import ClientError
service_resource = self.get_service_resource(aws_client)
try:
stack = service_resource.Stack(name=self.name)
stack.load()
creation_time = stack.creation_time
logger.debug(f"creation_time: {creation_time}")
if creation_time is not None:
logger.debug(f"Stack found: {stack.stack_name}")
self.active_resource = stack
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 CloudFormationStack
Args:
aws_client: The AwsApiClient for the current cluster
"""
print_info(f"Deleting {self.get_resource_type()}: {self.get_resource_name()}")
self.active_resource = None
try:
stack = self._read(aws_client)
logger.debug(f"Stack: {stack}")
if stack is None:
logger.warning(f"No {self.get_resource_type()} to delete")
return True
stack.delete()
# print_info("Stack deleted")
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 Stack 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("stack_delete_complete")
waiter.wait(
StackName=self.name,
WaiterConfig={
"Delay": self.waiter_delay,
"MaxAttempts": self.waiter_max_attempts,
},
)
return True
except Exception as e:
logger.error("Waiter failed.")
logger.error(e)
return True
def get_stack_resource(self, aws_client: AwsApiClient, logical_id: str) -> Optional[Any]:
# https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/cloudformation.html#CloudFormation.StackResource
# logger.debug(f"Getting StackResource {logical_id} for {self.name}")
try:
service_resource = self.get_service_resource(aws_client)
stack_resource = service_resource.StackResource(self.name, logical_id)
return stack_resource
except Exception as e:
logger.error(e)
return None
def get_stack_resource_physical_id(self, stack_resource: Any) -> Optional[str]:
# https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/cloudformation.html#CloudFormation.StackResource
try:
physical_resource_id = stack_resource.physical_resource_id if stack_resource is not None else None
logger.debug(f"{stack_resource.logical_id}: {physical_resource_id}")
return physical_resource_id
except Exception:
return None
def get_private_subnets(self, aws_client: Optional[AwsApiClient] = None) -> Optional[List[str]]:
try:
client: AwsApiClient = (
aws_client
if aws_client is not None
else AwsApiClient(aws_region=self.get_aws_region(), aws_profile=self.get_aws_profile())
)
private_subnets = []
private_subnet_1_stack_resource = self.get_stack_resource(client, "PrivateSubnet01")
private_subnet_1_physical_resource_id = self.get_stack_resource_physical_id(private_subnet_1_stack_resource)
if private_subnet_1_physical_resource_id is not None:
private_subnets.append(private_subnet_1_physical_resource_id)
private_subnet_2_stack_resource = self.get_stack_resource(client, "PrivateSubnet02")
private_subnet_2_physical_resource_id = self.get_stack_resource_physical_id(private_subnet_2_stack_resource)
if private_subnet_2_physical_resource_id is not None:
private_subnets.append(private_subnet_2_physical_resource_id)
private_subnet_3_stack_resource = self.get_stack_resource(client, "PrivateSubnet03")
private_subnet_3_physical_resource_id = self.get_stack_resource_physical_id(private_subnet_3_stack_resource)
if private_subnet_3_physical_resource_id is not None:
private_subnets.append(private_subnet_3_physical_resource_id)
return private_subnets if (len(private_subnets) > 0) else None
except Exception as e:
logger.error(e)
return None
def get_public_subnets(self, aws_client: Optional[AwsApiClient] = None) -> Optional[List[str]]:
try:
client: AwsApiClient = (
aws_client
if aws_client is not None
else AwsApiClient(aws_region=self.get_aws_region(), aws_profile=self.get_aws_profile())
)
public_subnets = []
public_subnet_1_stack_resource = self.get_stack_resource(client, "PublicSubnet01")
public_subnet_1_physical_resource_id = self.get_stack_resource_physical_id(public_subnet_1_stack_resource)
if public_subnet_1_physical_resource_id is not None:
public_subnets.append(public_subnet_1_physical_resource_id)
public_subnet_2_stack_resource = self.get_stack_resource(client, "PublicSubnet02")
public_subnet_2_physical_resource_id = self.get_stack_resource_physical_id(public_subnet_2_stack_resource)
if public_subnet_2_physical_resource_id is not None:
public_subnets.append(public_subnet_2_physical_resource_id)
return public_subnets if (len(public_subnets) > 0) else None
except Exception as e:
logger.error(e)
return None
def get_security_group(self, aws_client: Optional[AwsApiClient] = None) -> Optional[str]:
try:
client: AwsApiClient = (
aws_client
if aws_client is not None
else AwsApiClient(aws_region=self.get_aws_region(), aws_profile=self.get_aws_profile())
)
security_group_stack_resource = self.get_stack_resource(client, "ControlPlaneSecurityGroup")
security_group_physical_resource_id = (
security_group_stack_resource.physical_resource_id
if security_group_stack_resource is not None
else None
)
logger.debug(f"security_group: {security_group_physical_resource_id}")
return security_group_physical_resource_id
except Exception as e:
logger.error(e)
return None