From df4fbc8cb3cf04108abe5d037bd8829d354a5897 Mon Sep 17 00:00:00 2001 From: praveenraghav01 Date: Fri, 3 Mar 2023 10:49:44 +0530 Subject: [PATCH 1/3] added support for stack deletion --- cloudlift/__init__.py | 9 ++++ cloudlift/deployment/service_deletion.py | 60 ++++++++++++++++++++++++ cloudlift/version/__init__.py | 2 +- 3 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 cloudlift/deployment/service_deletion.py diff --git a/cloudlift/__init__.py b/cloudlift/__init__.py index 3c7e4adf..dae4d6e6 100644 --- a/cloudlift/__init__.py +++ b/cloudlift/__init__.py @@ -9,6 +9,7 @@ from cloudlift.deployment import EnvironmentCreator, editor from cloudlift.config.logging import log_err from cloudlift.deployment.service_creator import ServiceCreator +from cloudlift.deployment.service_deletion import ServiceDeletion from cloudlift.deployment.service_information_fetcher import ServiceInformationFetcher from cloudlift.deployment.service_updater import ServiceUpdater from cloudlift.deployment.task_definition_creator import TaskDefinitionCreator @@ -170,5 +171,13 @@ def start_session(name, environment, mfa, component): SessionCreator(name, environment).start_session(mfa, component) +@cli.command(help="Delete a service. This will delete all nested \ +ECS services") +@_require_environment +@_require_name +def delete_service(name, environment): + ServiceDeletion(name, environment).delete_stack() + + if __name__ == '__main__': cli() diff --git a/cloudlift/deployment/service_deletion.py b/cloudlift/deployment/service_deletion.py new file mode 100644 index 00000000..4bc9df1f --- /dev/null +++ b/cloudlift/deployment/service_deletion.py @@ -0,0 +1,60 @@ +from cloudlift.config import get_client_for, get_service_stack_name +from cloudlift.config import ParameterStore +from cloudlift.config.logging import log, log_err +from cloudlift.exceptions import UnrecoverableException + + +class ServiceDeletion(object): + ''' + Delete CloudFormation stack for ECS service and related dependencies + ''' + def __init__(self, name, environment): + self.name = name + self.environment = environment + self.stack_name = get_service_stack_name(environment, name) + self.client = get_client_for('cloudformation', self.environment) + self.client_iam = get_client_for('iam', self.environment) + self.init_stack_info() + + def init_stack_info(self): + self.stack_name = get_service_stack_name(self.environment, self.name) + try: + self.stack_resource = self.client.describe_stack_resources(StackName=self.stack_name)['StackResources'] + except Exception as e: + raise UnrecoverableException(e) + + def delete_stack(self): + log("Deleting CloudFormation stack " + self.stack_name + " ...") + self.delete_iam_role_policy() + try: + self.client.delete_stack(StackName=self.stack_name) + self.delete_ssm_parameter() + return True + except Exception as e: + return UnrecoverableException(e) + + def delete_iam_role_policy(self): + + for resource in self.stack_resource: + if resource['LogicalResourceId'].endswith("Role") and resource['LogicalResourceId'] != "ECSServiceRole": + log("Deleting IAM Role Policy for "| resource['LogicalResourceId'] + " resource") + try: + policies = self.client_iam.list_attached_role_policies(RoleName=resource['PhysicalResourceId']) + for policy in policies['AttachedPolicies']: + self.client_iam.detach_role_policy( + RoleName=resource['PhysicalResourceId'], PolicyArn=policy['PolicyArn']) + print(f"All the attached policies of {resource['PhysicalResourceId']} has been removed from IAM role") + except Exception as e: + raise UnrecoverableException(e) + + def delete_ssm_parameter(self): + log("Deleting SSM Parameters..") + parameter_store = ParameterStore(self.name, self.environment) + environment_configs, environment_configs_path = parameter_store.get_existing_config() + for k, v in environment_configs_path.items(): + try: + self.client.delete_parameter( + Name=v + ) + except Exception as e: + print(e) diff --git a/cloudlift/version/__init__.py b/cloudlift/version/__init__.py index 99d56a09..47f23af8 100644 --- a/cloudlift/version/__init__.py +++ b/cloudlift/version/__init__.py @@ -1 +1 @@ -VERSION = '1.5.7' \ No newline at end of file +VERSION = '2.0.0' \ No newline at end of file From c7999595c83414ef96208240f2c41eba77017128 Mon Sep 17 00:00:00 2001 From: praveenraghav01 Date: Thu, 9 Mar 2023 17:53:29 +0530 Subject: [PATCH 2/3] updated code --- cloudlift/deployment/service_deletion.py | 47 +++++++++++++++++++----- 1 file changed, 38 insertions(+), 9 deletions(-) diff --git a/cloudlift/deployment/service_deletion.py b/cloudlift/deployment/service_deletion.py index 4bc9df1f..894ccbe2 100644 --- a/cloudlift/deployment/service_deletion.py +++ b/cloudlift/deployment/service_deletion.py @@ -1,8 +1,11 @@ +from time import sleep + from cloudlift.config import get_client_for, get_service_stack_name from cloudlift.config import ParameterStore -from cloudlift.config.logging import log, log_err +from cloudlift.config.logging import log, log_err, log_bold from cloudlift.exceptions import UnrecoverableException - +from cloudlift.deployment.progress import get_stack_events, print_new_events +from botocore.exceptions import ClientError class ServiceDeletion(object): ''' @@ -14,30 +17,39 @@ def __init__(self, name, environment): self.stack_name = get_service_stack_name(environment, name) self.client = get_client_for('cloudformation', self.environment) self.client_iam = get_client_for('iam', self.environment) + self.client_ssm = get_client_for('ssm', environment) + self.existing_events = get_stack_events(self.client, self.stack_name) self.init_stack_info() def init_stack_info(self): self.stack_name = get_service_stack_name(self.environment, self.name) try: self.stack_resource = self.client.describe_stack_resources(StackName=self.stack_name)['StackResources'] - except Exception as e: - raise UnrecoverableException(e) + except ClientError as boto_client_error: + if boto_client_error.response['Error']['Code'] == 'ValidationError' and 'does not exist' in boto_client_error.response['Error']['Message']: + raise UnrecoverableException( + "Stack " + self.stack_name + " does not exist") + raise UnrecoverableException(boto_client_error) def delete_stack(self): - log("Deleting CloudFormation stack " + self.stack_name + " ...") self.delete_iam_role_policy() try: + log("Deleting CloudFormation stack " + self.stack_name + "...") self.client.delete_stack(StackName=self.stack_name) + self._print_progress() self.delete_ssm_parameter() return True - except Exception as e: - return UnrecoverableException(e) + except ClientError as boto_client_error: + if boto_client_error.response['Error']['Code'] == 'ValidationError' and 'does not exist' in boto_client_error.response['Error']['Message']: + raise UnrecoverableException( + "Stack " + self.stack_name + " does not exist") + raise UnrecoverableException(boto_client_error) def delete_iam_role_policy(self): for resource in self.stack_resource: if resource['LogicalResourceId'].endswith("Role") and resource['LogicalResourceId'] != "ECSServiceRole": - log("Deleting IAM Role Policy for "| resource['LogicalResourceId'] + " resource") + log("Deleting IAM Role Policy for "+ resource['LogicalResourceId'] + " resource") try: policies = self.client_iam.list_attached_role_policies(RoleName=resource['PhysicalResourceId']) for policy in policies['AttachedPolicies']: @@ -53,8 +65,25 @@ def delete_ssm_parameter(self): environment_configs, environment_configs_path = parameter_store.get_existing_config() for k, v in environment_configs_path.items(): try: - self.client.delete_parameter( + self.client_ssm.delete_parameter( Name=v ) except Exception as e: print(e) + + def _print_progress(self): + while True: + try: + response = self.client.describe_stacks(StackName=self.stack_name) + if "DELETE_IN_PROGRESS" not in response['Stacks'][0]['StackStatus']: + break + all_events = get_stack_events(self.client, self.stack_name) + print_new_events(all_events, self.existing_events) + self.existing_events = all_events + sleep(5) + except ClientError as boto_client_error: + if boto_client_error.response['Error']['Code'] == 'ValidationError' and 'does not exist' in boto_client_error.response['Error']['Message']: + log_bold( + "Stack " + self.stack_name + " deleted") + break + raise UnrecoverableException(boto_client_error) \ No newline at end of file From 8f2a4cc089c619e9230029962a55d0dc0eee277d Mon Sep 17 00:00:00 2001 From: praveenraghav01 Date: Fri, 10 Mar 2023 10:20:50 +0530 Subject: [PATCH 3/3] added error handling --- cloudlift/deployment/service_deletion.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/cloudlift/deployment/service_deletion.py b/cloudlift/deployment/service_deletion.py index 894ccbe2..560266e4 100644 --- a/cloudlift/deployment/service_deletion.py +++ b/cloudlift/deployment/service_deletion.py @@ -56,8 +56,12 @@ def delete_iam_role_policy(self): self.client_iam.detach_role_policy( RoleName=resource['PhysicalResourceId'], PolicyArn=policy['PolicyArn']) print(f"All the attached policies of {resource['PhysicalResourceId']} has been removed from IAM role") - except Exception as e: - raise UnrecoverableException(e) + except ClientError as boto_client_error: + if boto_client_error.response['Error']['Code'] == 'LimitExceededException': + log_bold("Limit Exceeded Exception, waiting for 5 sec..") + sleep(5) + continue + raise UnrecoverableException(boto_client_error) def delete_ssm_parameter(self): log("Deleting SSM Parameters..") @@ -68,8 +72,12 @@ def delete_ssm_parameter(self): self.client_ssm.delete_parameter( Name=v ) - except Exception as e: - print(e) + except ClientError as boto_client_error: + if boto_client_error.response['Error']['Code'] == 'ParameterNotFound': + log_err(f"Parameter {v} does not exist") + continue + else: + raise UnrecoverableException(boto_client_error) def _print_progress(self): while True: @@ -83,7 +91,6 @@ def _print_progress(self): sleep(5) except ClientError as boto_client_error: if boto_client_error.response['Error']['Code'] == 'ValidationError' and 'does not exist' in boto_client_error.response['Error']['Message']: - log_bold( - "Stack " + self.stack_name + " deleted") + log_bold("Stack " + self.stack_name + " deleted") break raise UnrecoverableException(boto_client_error) \ No newline at end of file