Spaces:
Runtime error
Runtime error
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 | |