{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Personalize Workshop Cleanup\n", "\n", "This notebook will walk through deleting all of the resources created by the CPG Personalize Immersion Day. You should only need to perform these steps if you have deployed in your own AWS account and want to deprovision the resources. If you are participating in an AWS-led workshop, this process is likely not necessary.\n", "\n", "This notebook uses the functions defined below, to iterate throught the resources inside a dataset group. The dataset group arn is saved from the Notebook `01_Data_Layer.pnyb`" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "%store -r" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import sys\n", "import getopt\n", "import logging\n", "import botocore\n", "import boto3\n", "import time\n", "from packaging import version\n", "from time import sleep\n", "from botocore.exceptions import ClientError\n", "\n", "logger = logging.getLogger()\n", "personalize = None\n", "\n", "def _get_dataset_group_arn(dataset_group_name):\n", " dsg_arn = None\n", "\n", " paginator = personalize.get_paginator('list_dataset_groups')\n", " for paginate_result in paginator.paginate():\n", " for dataset_group in paginate_result[\"datasetGroups\"]:\n", " if dataset_group['name'] == dataset_group_name:\n", " dsg_arn = dataset_group['datasetGroupArn']\n", " break\n", "\n", " if dsg_arn:\n", " break\n", "\n", " if not dsg_arn:\n", " raise NameError(f'Dataset Group \"{dataset_group_name}\" does not exist; verify region is correct')\n", "\n", " return dsg_arn\n", "\n", "def _get_solutions(dataset_group_arn):\n", " solution_arns = []\n", "\n", " paginator = personalize.get_paginator('list_solutions')\n", " for paginate_result in paginator.paginate(datasetGroupArn = dataset_group_arn):\n", " for solution in paginate_result['solutions']:\n", " solution_arns.append(solution['solutionArn'])\n", "\n", " return solution_arns\n", "\n", "def _delete_campaigns(solution_arns):\n", " campaign_arns = []\n", "\n", " for solution_arn in solution_arns:\n", " paginator = personalize.get_paginator('list_campaigns')\n", " for paginate_result in paginator.paginate(solutionArn = solution_arn):\n", " for campaign in paginate_result['campaigns']:\n", " if campaign['status'] in ['ACTIVE', 'CREATE FAILED']:\n", " logger.info('Deleting campaign: ' + campaign['campaignArn'])\n", "\n", " personalize.delete_campaign(campaignArn = campaign['campaignArn'])\n", " elif campaign['status'].startswith('DELETE'):\n", " logger.warning('Campaign {} is already being deleted so will wait for delete to complete'.format(campaign['campaignArn']))\n", " else:\n", " raise Exception('Campaign {} has a status of {} so cannot be deleted'.format(campaign['campaignArn'], campaign['status']))\n", "\n", " campaign_arns.append(campaign['campaignArn'])\n", "\n", " max_time = time.time() + 30*60 # 30 mins\n", " while time.time() < max_time:\n", " for campaign_arn in campaign_arns:\n", " try:\n", " describe_response = personalize.describe_campaign(campaignArn = campaign_arn)\n", " logger.debug('Campaign {} status is {}'.format(campaign_arn, describe_response['campaign']['status']))\n", " except ClientError as e:\n", " error_code = e.response['Error']['Code']\n", " if error_code == 'ResourceNotFoundException':\n", " campaign_arns.remove(campaign_arn)\n", "\n", " if len(campaign_arns) == 0:\n", " logger.info('All campaigns have been deleted or none exist for dataset group')\n", " break\n", " else:\n", " logger.info('Waiting for {} campaign(s) to be deleted'.format(len(campaign_arns)))\n", " time.sleep(20)\n", "\n", " if len(campaign_arns) > 0:\n", " raise Exception('Timed out waiting for all campaigns to be deleted')\n", "\n", "def _delete_solutions(solution_arns):\n", " for solution_arn in solution_arns:\n", " try:\n", " describe_response = personalize.describe_solution(solutionArn = solution_arn)\n", " solution = describe_response['solution']\n", " if solution['status'] in ['ACTIVE', 'CREATE FAILED']:\n", " logger.info('Deleting solution: ' + solution_arn)\n", "\n", " personalize.delete_solution(solutionArn = solution_arn)\n", " elif solution['status'].startswith('DELETE'):\n", " logger.warning('Solution {} is already being deleted so will wait for delete to complete'.format(solution_arn))\n", " else:\n", " raise Exception('Solution {} has a status of {} so cannot be deleted'.format(solution_arn, solution['status']))\n", " except ClientError as e:\n", " error_code = e.response['Error']['Code']\n", " if error_code != 'ResourceNotFoundException':\n", " raise e\n", "\n", " max_time = time.time() + 30*60 # 30 mins\n", " while time.time() < max_time:\n", " for solution_arn in solution_arns:\n", " try:\n", " describe_response = personalize.describe_solution(solutionArn = solution_arn)\n", " logger.debug('Solution {} status is {}'.format(solution_arn, describe_response['solution']['status']))\n", " except ClientError as e:\n", " error_code = e.response['Error']['Code']\n", " if error_code == 'ResourceNotFoundException':\n", " solution_arns.remove(solution_arn)\n", "\n", " if len(solution_arns) == 0:\n", " logger.info('All solutions have been deleted or none exist for dataset group')\n", " break\n", " else:\n", " logger.info('Waiting for {} solution(s) to be deleted'.format(len(solution_arns)))\n", " time.sleep(20)\n", "\n", " if len(solution_arns) > 0:\n", " raise Exception('Timed out waiting for all solutions to be deleted')\n", "\n", "def _delete_event_trackers(dataset_group_arn):\n", " event_tracker_arns = []\n", "\n", " event_trackers_paginator = personalize.get_paginator('list_event_trackers')\n", " for event_tracker_page in event_trackers_paginator.paginate(datasetGroupArn = dataset_group_arn):\n", " for event_tracker in event_tracker_page['eventTrackers']:\n", " if event_tracker['status'] in [ 'ACTIVE', 'CREATE FAILED' ]:\n", " logger.info('Deleting event tracker {}'.format(event_tracker['eventTrackerArn']))\n", " personalize.delete_event_tracker(eventTrackerArn = event_tracker['eventTrackerArn'])\n", " elif event_tracker['status'].startswith('DELETE'):\n", " logger.warning('Event tracker {} is already being deleted so will wait for delete to complete'.format(event_tracker['eventTrackerArn']))\n", " else:\n", " raise Exception('Solution {} has a status of {} so cannot be deleted'.format(event_tracker['eventTrackerArn'], event_tracker['status']))\n", "\n", " event_tracker_arns.append(event_tracker['eventTrackerArn'])\n", "\n", " max_time = time.time() + 30*60 # 30 mins\n", " while time.time() < max_time:\n", " for event_tracker_arn in event_tracker_arns:\n", " try:\n", " describe_response = personalize.describe_event_tracker(eventTrackerArn = event_tracker_arn)\n", " logger.debug('Event tracker {} status is {}'.format(event_tracker_arn, describe_response['eventTracker']['status']))\n", " except ClientError as e:\n", " error_code = e.response['Error']['Code']\n", " if error_code == 'ResourceNotFoundException':\n", " event_tracker_arns.remove(event_tracker_arn)\n", "\n", " if len(event_tracker_arns) == 0:\n", " logger.info('All event trackers have been deleted or none exist for dataset group')\n", " break\n", " else:\n", " logger.info('Waiting for {} event tracker(s) to be deleted'.format(len(event_tracker_arns)))\n", " time.sleep(20)\n", "\n", " if len(event_tracker_arns) > 0:\n", " raise Exception('Timed out waiting for all event trackers to be deleted')\n", "\n", "def _delete_filters(dataset_group_arn):\n", " filter_arns = []\n", "\n", " filters_response = personalize.list_filters(datasetGroupArn = dataset_group_arn, maxResults = 100)\n", " for filter in filters_response['Filters']:\n", " logger.info('Deleting filter ' + filter['filterArn'])\n", " personalize.delete_filter(filterArn = filter['filterArn'])\n", " filter_arns.append(filter['filterArn'])\n", "\n", " max_time = time.time() + 30*60 # 30 mins\n", " while time.time() < max_time:\n", " for filter_arn in filter_arns:\n", " try:\n", " describe_response = personalize.describe_filter(filterArn = filter_arn)\n", " logger.debug('Filter {} status is {}'.format(filter_arn, describe_response['filter']['status']))\n", " except ClientError as e:\n", " error_code = e.response['Error']['Code']\n", " if error_code == 'ResourceNotFoundException':\n", " filter_arns.remove(filter_arn)\n", "\n", " if len(filter_arns) == 0:\n", " logger.info('All filters have been deleted or none exist for dataset group')\n", " break\n", " else:\n", " logger.info('Waiting for {} filter(s) to be deleted'.format(len(filter_arns)))\n", " time.sleep(20)\n", "\n", " if len(filter_arns) > 0:\n", " raise Exception('Timed out waiting for all filter to be deleted')\n", "\n", "def _delete_datasets_and_schemas(dataset_group_arn):\n", " dataset_arns = []\n", " schema_arns = []\n", "\n", " dataset_paginator = personalize.get_paginator('list_datasets')\n", " for dataset_page in dataset_paginator.paginate(datasetGroupArn = dataset_group_arn):\n", " for dataset in dataset_page['datasets']:\n", " describe_response = personalize.describe_dataset(datasetArn = dataset['datasetArn'])\n", " schema_arns.append(describe_response['dataset']['schemaArn'])\n", "\n", " if dataset['status'] in ['ACTIVE', 'CREATE FAILED']:\n", " logger.info('Deleting dataset ' + dataset['datasetArn'])\n", " personalize.delete_dataset(datasetArn = dataset['datasetArn'])\n", " elif dataset['status'].startswith('DELETE'):\n", " logger.warning('Dataset {} is already being deleted so will wait for delete to complete'.format(dataset['datasetArn']))\n", " else:\n", " raise Exception('Dataset {} has a status of {} so cannot be deleted'.format(dataset['datasetArn'], dataset['status']))\n", "\n", " dataset_arns.append(dataset['datasetArn'])\n", "\n", " max_time = time.time() + 30*60 # 30 mins\n", " while time.time() < max_time:\n", " for dataset_arn in dataset_arns:\n", " try:\n", " describe_response = personalize.describe_dataset(datasetArn = dataset_arn)\n", " logger.debug('Dataset {} status is {}'.format(dataset_arn, describe_response['dataset']['status']))\n", " except ClientError as e:\n", " error_code = e.response['Error']['Code']\n", " if error_code == 'ResourceNotFoundException':\n", " dataset_arns.remove(dataset_arn)\n", "\n", " if len(dataset_arns) == 0:\n", " logger.info('All datasets have been deleted or none exist for dataset group')\n", " break\n", " else:\n", " logger.info('Waiting for {} dataset(s) to be deleted'.format(len(dataset_arns)))\n", " time.sleep(20)\n", "\n", " if len(dataset_arns) > 0:\n", " raise Exception('Timed out waiting for all datasets to be deleted')\n", "\n", " for schema_arn in schema_arns:\n", " try:\n", " logger.info('Deleting schema ' + schema_arn)\n", " personalize.delete_schema(schemaArn = schema_arn)\n", " except ClientError as e:\n", " error_code = e.response['Error']['Code']\n", " if error_code == 'ResourceInUseException':\n", " logger.info('Schema {} is still in-use by another dataset (likely in another dataset group)'.format(schema_arn))\n", " else:\n", " raise e\n", "\n", " logger.info('All schemas used exclusively by datasets have been deleted or none exist for dataset group')\n", "\n", "def _delete_dataset_group(dataset_group_arn):\n", " logger.info('Deleting dataset group ' + dataset_group_arn)\n", " personalize.delete_dataset_group(datasetGroupArn = dataset_group_arn)\n", "\n", " max_time = time.time() + 30*60 # 30 mins\n", " while time.time() < max_time:\n", " try:\n", " describe_response = personalize.describe_dataset_group(datasetGroupArn = dataset_group_arn)\n", " logger.debug('Dataset group {} status is {}'.format(dataset_group_arn, describe_response['datasetGroup']['status']))\n", " break\n", " except ClientError as e:\n", " error_code = e.response['Error']['Code']\n", " if error_code == 'ResourceNotFoundException':\n", " logger.info('Dataset group {} has been fully deleted'.format(dataset_group_arn))\n", " else:\n", " raise e\n", "\n", " logger.info('Waiting for dataset group to be deleted')\n", " time.sleep(20)\n", "\n", "def delete_dataset_groups(dataset_group_arns, region = None):\n", " global personalize\n", " personalize = boto3.client(service_name = 'personalize', region_name = region)\n", "\n", " for dataset_group_arn in dataset_group_arns:\n", " logger.info('Dataset Group ARN: ' + dataset_group_arn)\n", "\n", " solution_arns = _get_solutions(dataset_group_arn)\n", "\n", " # 1. Delete campaigns\n", " _delete_campaigns(solution_arns)\n", "\n", " # 2. Delete solutions\n", " _delete_solutions(solution_arns)\n", "\n", " # 3. Delete event trackers\n", " _delete_event_trackers(dataset_group_arn)\n", "\n", " # 4. Delete filters\n", " _delete_filters(dataset_group_arn)\n", "\n", " # 5. Delete datasets and their schemas\n", " _delete_datasets_and_schemas(dataset_group_arn)\n", "\n", " # 6. Delete dataset group\n", " _delete_dataset_group(dataset_group_arn)\n", "\n", " logger.info(f'Dataset group {dataset_group_arn} fully deleted')\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "delete_dataset_groups([dataset_group_arn], region)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Clean up the S3 bucket and IAM role\n", "\n", "Start by deleting the role, then empty the bucket, then delete the bucket." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "iam = boto3.client('iam')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Identify the name of the role you want to delete.\n", "\n", "You cannot delete an IAM role which still has policies attached to it. So after you have identified the relevant role, let's list the attached policies of that role." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "iam.list_attached_role_policies(\n", " RoleName = role_name\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You need to detach the policies in the result above using the code below. Repeat for each attached policy." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "iam.detach_role_policy(\n", " RoleName = role_name,\n", " PolicyArn = 'arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess'\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Finally, you should be able to delete the IAM role." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "iam.delete_role(\n", " RoleName = role_name\n", ")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To delete an S3 bucket, it first needs to be empty. The easiest way to delete an S3 bucket, is just to navigate to S3 in the AWS console, delete the objects in the bucket, and then delete the S3 bucket itself." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Deleting the Automation from the Operations Notebook" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "stack_name = \"notebook-automation\"\n", "bucket= !aws cloudformation describe-stacks --stack-name $stack_name --query \"Stacks[0].Outputs[?OutputKey=='InputBucketName'].OutputValue\" --output text\n", "bucket_name = bucket[0]\n", "print(bucket)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "!aws s3 rb s3://$bucket_name --force" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "!aws cloudformation delete-stack --stack-name $stack_name\n", "time.sleep(30)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "!aws cloudformation describe-stacks --stack-name $stack_name" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Deleting the Automation from the Initial CloudFormation deployment" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "stack_name = \"id-ml-ops\"\n", "bucket= !aws cloudformation describe-stacks --stack-name $stack_name --query \"Stacks[0].Outputs[?OutputKey=='InputBucketName'].OutputValue\" --output text\n", "bucket_name = bucket[0]\n", "print(bucket_name)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "!aws s3 rb s3://$bucket_name --force" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "!aws cloudformation delete-stack --stack-name $stack_name\n", "time.sleep(120)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "!aws cloudformation describe-stacks --stack-name $stack_name" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Deleting the bucket with the automation artifacts" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "stack_name = \"AmazonPersonalizeImmersionDay\"\n", "bucket = !aws cloudformation describe-stack-resources --stack-name $stack_name --logical-resource-id SAMArtifactsBucket --query \"StackResources[0].PhysicalResourceId\" --output text\n", "bucket_name = bucket[0]\n", "print(bucket_name)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "!aws s3 rb s3://$bucket_name --force" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now you can navigate to your [Cloudformation console](https://console.aws.amazon.com/cloudformation/) and delete the **AmazonPersonalizeImmersionDay** stack" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Cleanup Complete\n", "\n", "All resources created by the Personalize workshop have been deleted." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "conda_python3", "language": "python", "name": "conda_python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.6.13" } }, "nbformat": 4, "nbformat_minor": 4 }