{ "cells": [ { "cell_type": "markdown", "id": "c590490b-9b00-4265-a555-dd2af86f8d9f", "metadata": { "tags": [] }, "source": [ "# Kinesis Video Streams Sample Application\n", "\n", "This notebook shows how to create an application for Panorama to support multiple cameras output via Amazon Kinesis Video Streams.\n", "\n", "By completing this notebook, you will learn:\n", "\n", "- How to write a Python script for your application that creates multiple real-time video streaming via Amazon Kinesis Video Streams and AWS IoT\n", "- How to programmatically package and deploy applications using the Panorama CLI\n", "- How to use abstract camera node and input node to override the configuration to support multiple cameras and Amazon Kinesis Video Streams programmatically" ] }, { "cell_type": "markdown", "id": "b5d281b2-79e4-4420-baed-caa115211ebb", "metadata": {}, "source": [ "---\n", "1. Prerequisites\n", "2. Set up\n", "3. Package application\n", "4. Deploy application to device\n", "5. Clean up" ] }, { "cell_type": "markdown", "id": "8ab81257-f70a-43d8-ad80-c9260fba5ddb", "metadata": {}, "source": [ "# Prerequisites" ] }, { "cell_type": "markdown", "id": "4346ca45-514a-485d-8ea9-17dd36bcb5e8", "metadata": {}, "source": [ "In a terminal session on this Jupyter notebook server, run `aws configure`. This allows this notebook server to access Panorama resources and deploy applications on your behalf." ] }, { "cell_type": "markdown", "id": "bebafe3b-bf10-411b-9b47-6fa34694b5b9", "metadata": {}, "source": [ "# Set up" ] }, { "cell_type": "markdown", "id": "764a3515-099f-439b-a40a-2c1d409d5dbc", "metadata": {}, "source": [ "Import libraries for use with this notebook environment, you do not need these libraries when you write your application code." ] }, { "cell_type": "code", "execution_count": null, "id": "c3ac3dc9-a1d4-4af5-9075-3dbef5d3c5d5", "metadata": {}, "outputs": [], "source": [ "import sys\n", "import os\n", "import time\n", "import json\n", "import uuid\n", "\n", "import boto3\n", "\n", "sys.path.insert( 0, os.path.abspath( '../common/test_utility' ) )\n", "import panorama_test_utility" ] }, { "cell_type": "code", "execution_count": null, "id": "d2de5b0e-7606-4304-8fcf-5779da3787e0", "metadata": {}, "outputs": [], "source": [ "account_id = boto3.client('sts').get_caller_identity()['Account']\n", "region_name = boto3.session.Session().region_name\n", "app_name = 'kinesis_video_streams_app'\n", "code_package_name = 'kinesis_video_streams_code'\n", "\n", "print( 'account_id :', account_id )\n", "print( 'region_name :', region_name )\n", "\n", "# Following configurations are required when you use real hardware, \n", "# thus can be any dummy strings when you use only Test Utility.\n", "device_id = input( \"Device Id (format : device-*)\" ).strip()\n", "\n", "# Enter application role to be deployed in panorama device (Role with permission for accessing AWS IoT is a must.\n", "# You can grant other AWS service permissions to this role if needed.) Format: arn:aws:iam::{account}:role/{roleName}\n", "application_role_arn = input( 'Application ROLE ARN' ).strip()\n", "\n", "print(device_id)" ] }, { "cell_type": "markdown", "id": "fd835ac5-0ae7-4cf2-9cf3-8c06725377d1", "metadata": {}, "source": [ "## Set up application" ] }, { "cell_type": "markdown", "id": "c3fe6bcc-3821-435b-b91e-2db4dc7a8db5", "metadata": {}, "source": [ "Every application uses the creator's AWS Account ID as the prefix to uniquely identifies the application resources. Running `panorama-cli import-application` replaces the generic account Id with your account Id." ] }, { "cell_type": "code", "execution_count": null, "id": "b61d6d63-c999-4f75-b26d-af9d06154470", "metadata": {}, "outputs": [], "source": [ "!cd ./kinesis_video_streams_app/ && panorama-cli import-application" ] }, { "cell_type": "markdown", "id": "16e4272a-a020-4744-bd94-58ed5fe35839", "metadata": {}, "source": [ "## Create AWS IoT Thing and credential" ] }, { "cell_type": "markdown", "id": "cdb89596-2d8b-4b02-91cf-7cacd3d149b4", "metadata": {}, "source": [ "To obtain Amazon Kinesis Video Streams service access token, there are two ways to get it: one is to use [Identity and Access Management(IAM)](https://docs.aws.amazon.com/kinesisvideostreams/latest/dg/how-iam.html) and the other is to use [AWS IoT thing's credential](https://docs.aws.amazon.com/kinesisvideostreams/latest/dg/how-iot.html). In order to support multiple cameras without creating too many IAM roles, using AWS IoT Thing credential to represent camera is highly recommended for managing control access policy. Please follow the steps to create the related AWS IoT Thing, credential and policy." ] }, { "cell_type": "code", "execution_count": null, "id": "826ecf87-f7c3-4c8c-b9c2-3f327195785f", "metadata": {}, "outputs": [], "source": [ "# Please modify prefix if needed.\n", "IAM_ROLE_NAME = 'KinesesVideoStreamsPanramaSampleIAMRole' + str(int(time.time()))\n", "IAM_POLICY_NAME = 'KinesesVideoStreamsPanramaSampleIAMPolicy' + str(int(time.time()))\n", "IOT_ROLE_ALIAS_NAME = 'KinesesVideoStreamsPanramaSampleIotRoleAlias' + str(int(time.time()))\n", "IOT_POLICY_NAME = 'KinesesVideoStreamsPanramaSampleIotPolicy' + str(int(time.time()))\n", "\n", "IOT_CERT_ARN = {}\n", "IOT_PUBLIC_KEY = {}\n", "IOT_PRIVATE_KEY = {}\n", "IOT_CERT_PEM = {}\n", "IOT_CERT_ID = {}\n", "\n", "iam_client = boto3.client('iam')\n", "iot_client = boto3.client('iot')\n", "\n", "# Load sample assume role policy from templates/.\n", "with open('./templates/iam-policy-document.json') as iam_policy_json:\n", " iam_policy_payload = json.load(iam_policy_json)\n", " try:\n", " res = iam_client.create_role(\n", " RoleName=IAM_ROLE_NAME,\n", " AssumeRolePolicyDocument=json.dumps(iam_policy_payload)\n", " )\n", " IAM_ROLE_ARN = res['Role']['Arn']\n", " except Exception as e:\n", " print('Failed on IAM create_role')\n", " print(e)\n", "\n", "# Load sample kinesis video streams policy from templates/.\n", "with open(\"./templates/iam-permission-document.json\") as iam_permission_json:\n", " iam_permission_payload = json.load(iam_permission_json)\n", " try:\n", " res = iam_client.put_role_policy(\n", " RoleName=IAM_ROLE_NAME,\n", " PolicyName=IAM_POLICY_NAME,\n", " PolicyDocument=json.dumps(iam_permission_payload)\n", " )\n", " except Exception as e:\n", " print('Failed on IAM put_role_policy')\n", " print(e)\n", "\n", "# Create IoT Role Alias.\n", "try:\n", " res = iot_client.create_role_alias(\n", " roleAlias=IOT_ROLE_ALIAS_NAME,\n", " roleArn=IAM_ROLE_ARN,\n", " credentialDurationSeconds=3600\n", " )\n", " IOT_ROLE_ALIAS_ARN = res['roleAliasArn']\n", "except Exception as e:\n", " print('Failed on IoT create_role_alias')\n", " print(e)\n", " \n", "# Create IoT Policy and attach role alias.\n", "with open(\"./templates/iot-policy-document.json\") as iot_policy_json:\n", " iot_policy_payload = json.load(iot_policy_json)\n", " for statement in iot_policy_payload['Statement']:\n", " statement['Resource'] = IOT_ROLE_ALIAS_ARN\n", " \n", " try:\n", " res = iot_client.create_policy(\n", " policyName=IOT_POLICY_NAME,\n", " policyDocument=json.dumps(iot_policy_payload)\n", " )\n", " except Exception as e:\n", " print('Failed on IoT create_policy')\n", " print(e)\n", "\n", "# Create IoT Thing, here we use camera name as AWS IoT Thing Name.\n", "# These camera names will also be used to crate AWS Panorama Data Sources in the following step.\n", "CAMERAS = [x.strip() for x in input(\"Please input with the unique camera names (format : cam-1,cam-2,...): \").split(\",\")]\n", "cert_path = f'./{app_name}/packages/{account_id}-{code_package_name}-1.0/src/certs/'\n", "\n", "for CAMERA in CAMERAS:\n", " # Create IoT Thing.\n", " try:\n", " res = iot_client.create_thing(thingName=CAMERA)\n", " except Exception as e:\n", " print('Failed on IoT create_thing')\n", " print(e)\n", "\n", " # Create IoT certificate, private key and public key. Then save them in target folder.\n", " try:\n", " data = iot_client.create_keys_and_certificate(setAsActive = True)\n", " credentials = json.loads(json.dumps(data, sort_keys=False, indent=4))\n", " \n", " IOT_CERT_ARN[CAMERA] = credentials['certificateArn']\n", " IOT_PUBLIC_KEY[CAMERA] = credentials['keyPair']['PublicKey']\n", " IOT_PRIVATE_KEY[CAMERA] = credentials['keyPair']['PrivateKey']\n", " IOT_CERT_PEM[CAMERA] = credentials['certificatePem']\n", " IOT_CERT_ID[CAMERA] = credentials['certificateId']\n", " \n", " with open(f'{cert_path}/{CAMERA}-public.key', 'w') as outfile:\n", " outfile.write(IOT_PUBLIC_KEY[CAMERA])\n", " \n", " with open(f'{cert_path}/{CAMERA}-private.key', 'w') as outfile:\n", " outfile.write(IOT_PRIVATE_KEY[CAMERA])\n", " \n", " with open(f'{cert_path}/{CAMERA}-cert.pem', 'w') as outfile:\n", " outfile.write(IOT_CERT_PEM[CAMERA])\n", " except Exception as e:\n", " print('Failed on IoT create_keys_and_certificate')\n", " print(e)\n", " \n", " # Attach IoT policy to certificate and attach certificate to IoT thing.\n", " try:\n", " res = iot_client.attach_policy(\n", " policyName = IOT_POLICY_NAME,\n", " target = IOT_CERT_ARN[CAMERA]\n", " )\n", " except Exception as e:\n", " print('Failed on IoT attach_policy')\n", " print(e)\n", " \n", " # Attach certificate to IoT thing.\n", " try:\n", " res = iot_client.attach_thing_principal(\n", " thingName = CAMERA,\n", " principal = IOT_CERT_ARN[CAMERA]\n", " )\n", " except Exception as e:\n", " print('Failed on IoT attach_thing_principal')\n", " print(e)" ] }, { "cell_type": "markdown", "id": "2c53a1aa-8c29-4b3e-b4bf-de9b969189b0", "metadata": { "tags": [] }, "source": [ "# Package app" ] }, { "cell_type": "markdown", "id": "4e2350d4-7926-41f5-ba6d-367e33d20175", "metadata": { "tags": [] }, "source": [ "## Overriding camera node and Amazon Kinesis Video Streams configuration" ] }, { "cell_type": "markdown", "id": "075759cf-ae44-4cc1-ad11-511c07b087d2", "metadata": {}, "source": [ "In this sample, we could support multiple cameras and multiple Amazon KVS streams. Please follow the steps to create override json documentation(in kinesis_video_streams_app/deployment_overrides/override_configuration.json)." ] }, { "cell_type": "code", "execution_count": null, "id": "f6898e92-b0d1-4761-a1aa-9d03d84022c7", "metadata": {}, "outputs": [], "source": [ "# Loading override template and update camera node information\n", "with open(f\"./{app_name}/deployment_overrides/override_configuration_template.json\") as override_configuration:\n", " override_configuration_payload = json.load(override_configuration)\n", " for CAMERA in CAMERAS:\n", " override_configuration_payload['nodeGraphOverrides']['packages'].append({\n", " 'name': '{acc_id}::{name}'.format(acc_id=account_id, name=CAMERA),\n", " 'version': '1.0'\n", " })\n", " override_configuration_payload['nodeGraphOverrides']['nodes'].append({\n", " 'name': '{}'.format(CAMERA),\n", " 'interface': '{acc_id}::{name}.{name}'.format(acc_id=account_id, name=CAMERA),\n", " 'overridable': True,\n", " 'overrideMandatory': False,\n", " 'launch': \"onAppStart\"\n", " })\n", " override_configuration_payload['nodeGraphOverrides']['nodeOverrides'][0]['with'].append({\n", " 'name': '{}'.format(CAMERA)\n", " })\n", "\n", "# Update kvs_stream_name, using camera name as kvs stream name\n", "kvs_stream_names = ','.join(CAMERAS)\n", "override_configuration_payload['nodeGraphOverrides']['nodes'].append({\n", " 'name': 'override_kvs_stream_name',\n", " \"interface\": \"string\",\n", " \"value\": '{}'.format(kvs_stream_names),\n", " \"overridable\": True,\n", "})\n", "\n", "# Update AWS IoT role alias\n", "override_configuration_payload['nodeGraphOverrides']['nodes'].append({\n", " 'name': 'override_iot_role_alias',\n", " \"interface\": \"string\",\n", " \"value\": '{}'.format(IOT_ROLE_ALIAS_NAME),\n", " \"overridable\": True,\n", "})\n", "\n", "# Update Amazon KVS service region. Theoretically we could support running Panorama in one region and servicing Amazon KVS in another region\n", "override_configuration_payload['nodeGraphOverrides']['nodes'].append({\n", " 'name': 'override_kvs_region',\n", " \"interface\": \"string\",\n", " \"value\": '{}'.format(region_name),\n", " \"overridable\": True,\n", "})\n", "\n", "# Create and write content to override documentation\n", "with open(f\"./{app_name}/deployment_overrides/override_configuration.json\", \"w\") as override_configuration_final:\n", " json.dump(override_configuration_payload, override_configuration_final, indent=4)" ] }, { "cell_type": "markdown", "id": "fc9b5684-76ad-4fd3-a0dd-a43373caede2", "metadata": {}, "source": [ "### Preview override-manifest file(\"override_configuration.json\")" ] }, { "cell_type": "code", "execution_count": null, "id": "fa2a7cd7-71bd-4506-9a1c-d16a9d652919", "metadata": {}, "outputs": [], "source": [ "panorama_test_utility.preview_text_file( f\"./{app_name}/deployment_overrides/override_configuration.json\" )" ] }, { "cell_type": "markdown", "id": "6e22160a-9001-4792-acb6-55320458bb26", "metadata": { "tags": [] }, "source": [ "### Create New Camera\n", "\n", "Because we are using an ```abstract_rtsp_media_source```, we have to create a camera or serval cameras before we can use the ```abstract_rtsp_media_source```\n", "\n", "**NOTE** : Update your RTSP Info in the next cell, Username, Password and RTSP Stream URL" ] }, { "cell_type": "code", "execution_count": null, "id": "7502ccb5-2a3d-49ed-8d3e-6bc3a807a27a", "metadata": {}, "outputs": [], "source": [ "panorama_client = boto3.client('panorama')\n", "\n", "for CAMERA in CAMERAS:\n", " # Please input your camera credential.\n", " print('You are going to input credential for {}'.format(CAMERA))\n", " CAMERA_CREDS = {\n", " \"Username\": input(\"Please input with user name of camera: \"),\n", " \"Password\": input(\"Please input with password of camera: \"),\n", " \"StreamUrl\": input(\"Please input with rtsp address of camera including port: \")\n", " }\n", "\n", " try:\n", " res = panorama_client.create_node_from_template_job(\n", " NodeName=CAMERA,\n", " OutputPackageName=CAMERA,\n", " OutputPackageVersion='1.0',\n", " TemplateParameters=CAMERA_CREDS,\n", " TemplateType='RTSP_CAMERA_STREAM'\n", " )\n", " print(res)\n", " except Exception as e:\n", " print(f'Failed on Panorama create_node_from_template_job :{CAMERA}')\n", " print(e)" ] }, { "cell_type": "markdown", "id": "586100e7-5da4-4af8-a66f-41f7c4f171c8", "metadata": {}, "source": [ "### Build application with container" ] }, { "cell_type": "code", "execution_count": null, "id": "da76590c-4900-4bd4-835e-67a2e90a61cb", "metadata": {}, "outputs": [], "source": [ "container_asset_name = 'kinesis_video_streams_code'" ] }, { "cell_type": "code", "execution_count": null, "id": "7fe2b97c-9cce-40b0-a8c1-dd5fa2d7500b", "metadata": {}, "outputs": [], "source": [ "%%capture captured_output\n", "\n", "# Building container image.This process takes time (5min ~ 10min)\n", "# FIXME : without %%capture, browser tab crashes because of too much output from the command.\n", "\n", "!cd ./kinesis_video_streams_app && panorama-cli build \\\n", " --container-asset-name {container_asset_name} \\\n", " --package-path packages/{account_id}-{code_package_name}-1.0" ] }, { "cell_type": "code", "execution_count": null, "id": "7da5bd57-a052-428b-8e44-33539212dcbd", "metadata": {}, "outputs": [], "source": [ "stdout_lines = captured_output.stdout.splitlines()\n", "stderr_lines = captured_output.stderr.splitlines()\n", "print(\" :\")\n", "print(\" :\")\n", "for line in stdout_lines[-30:] + stderr_lines[-30:]:\n", " print(line)" ] }, { "cell_type": "markdown", "id": "850939d6-444d-436a-8c3a-be5484006ea1", "metadata": {}, "source": [ "### Upload application to Panorama for deploying to devices" ] }, { "cell_type": "code", "execution_count": null, "id": "cdf15c12-fa78-490e-ac16-9d40c5fd6071", "metadata": {}, "outputs": [], "source": [ "# This step takes some time, depending on your network environment.\n", "!cd ./kinesis_video_streams_app && panorama-cli package-application" ] }, { "cell_type": "markdown", "id": "9e0c7cfb-d922-4e05-a8ee-03bbc181535b", "metadata": {}, "source": [ "### Ready for deploying to a device\n", "\n", "Congrats! Your app is now ready to deploy to a device. Next, you can continue in this notebook to deploy the app programmatically or you can go to the Panorama console and deploying using the AWS Console. The console makes it easier to select camera streams and select the devices you want to deploy to. Programmatic deployment is faster to complete and easier to automate." ] }, { "cell_type": "markdown", "id": "f587ab9f-2485-46e7-af93-2dbb6e90a408", "metadata": {}, "source": [ "# Deploy application to device" ] }, { "cell_type": "markdown", "id": "565b7163-09ad-4de2-8bce-b0c01aaf9aff", "metadata": {}, "source": [ "Let's make sure the device we are deploying to is available." ] }, { "cell_type": "code", "execution_count": null, "id": "69579246-f808-4883-9d1f-bd3067d04f45", "metadata": {}, "outputs": [], "source": [ "response = panorama_client.describe_device(\n", " DeviceId= device_id\n", ")\n", "\n", "print('You are deploying to Device: {}'.format(response['Name']))" ] }, { "cell_type": "markdown", "id": "60636e61-f813-4815-bafe-af336304e441", "metadata": {}, "source": [ "## Deploy application\n", "\n", "You are ready to deploy your app." ] }, { "cell_type": "code", "execution_count": null, "id": "4a5947f2-f8c1-4eab-bc21-d6f93502729e", "metadata": {}, "outputs": [], "source": [ "with open(f\"./{app_name}/graphs/{app_name}/graph.json\") as fd:\n", " manifest_payload = json.load(fd)\n", " \n", "with open(f\"./{app_name}/deployment_overrides/override_configuration.json\") as fd:\n", " override_payload = json.load(fd)\n" ] }, { "cell_type": "code", "execution_count": null, "id": "6a3a3f8e-8894-4da2-adad-118404fa06a1", "metadata": {}, "outputs": [], "source": [ "try:\n", " resp = panorama_client.create_application_instance(\n", " Name=app_name,\n", " ManifestPayload={'PayloadData': json.dumps(manifest_payload)},\n", " ManifestOverridesPayload={'PayloadData': json.dumps(override_payload)},\n", " DefaultRuntimeContextDevice=device_id,\n", " RuntimeRoleArn=application_role_arn\n", " )\n", "except Exception as e:\n", " # raise e\n", " print(e)" ] }, { "cell_type": "code", "execution_count": null, "id": "4905f5f2-6480-484f-83b6-bf7bc6ed9d32", "metadata": {}, "outputs": [], "source": [ "app_id = resp['ApplicationInstanceId']\n", "print( \"Application Instance Id :\", app_id )\n", "\n", "progress_dots = panorama_test_utility.ProgressDots()\n", "while True:\n", " response = panorama_client.describe_application_instance( ApplicationInstanceId = app_id )\n", " status = response['Status']\n", " progress_dots.update_status( f'{status} ({response[\"StatusDescription\"]})' )\n", " if status in ('DEPLOYMENT_SUCCEEDED','DEPLOYMENT_FAILED'):\n", " break\n", " time.sleep(60)" ] }, { "cell_type": "markdown", "id": "742a7d04-30ef-4aca-aaa7-08bdc1b5a8a8", "metadata": {}, "source": [ "## Verify from Amazon Kinesis Video Streams console" ] }, { "cell_type": "code", "execution_count": null, "id": "89c4bf5a-33dc-4548-b0b4-60a294dc52be", "metadata": {}, "outputs": [], "source": [ "# Get Amazon Kinesis Video Streams HLS playback url\n", "kvs_client = boto3.client('kinesisvideo')\n", "\n", "for CAMERA in CAMERAS:\n", " # Get data endpoint\n", " KVS_DATA_ENDPOINT = kvs_client.get_data_endpoint(\n", " StreamName=CAMERA,\n", " APIName='GET_HLS_STREAMING_SESSION_URL'\n", " )['DataEndpoint']\n", " \n", " kvs_media_client = boto3.client('kinesis-video-archived-media',endpoint_url=KVS_DATA_ENDPOINT)\n", " try:\n", " # Get live HLS stream url for 12 hours(43200 minutes)\n", " KVS_HLS_URL = kvs_media_client.get_hls_streaming_session_url(\n", " StreamName=CAMERA,\n", " PlaybackMode='LIVE',\n", " Expires=43200\n", " )['HLSStreamingSessionURL']\n", " print(KVS_HLS_URL)\n", " except Exception as e:\n", " print(f'Failed on get_hls_streaming_session_url :{CAMERA}')\n", " print(e)\n", " \n", "print('Paste the URL on Safari, QuickTime Player, or player that supports streaming in HLS format, such as hls.js')\n", "print('If you want to check the video stream on Amazon Kinesis Video Streams console, Chrome browser is highly recommended')" ] }, { "cell_type": "code", "execution_count": null, "id": "b5aff31f-270d-47f7-a24b-0fb72afe4acb", "metadata": {}, "outputs": [], "source": [ "# Store the arn of Amazon Kinesis Video Streams for cleaning up.\n", "KVS_STREAM_ARN = {}\n", "\n", "for CAMERA in CAMERAS:\n", " try:\n", " res = kvs_client.describe_stream(\n", " StreamName=CAMERA\n", " )\n", " KVS_STREAM_ARN[CAMERA] = res['StreamInfo']['StreamARN']\n", " except Exception as e:\n", " print(f'Failed on get arn, video stream name :{CAMERA}')\n", " print(e)" ] }, { "cell_type": "markdown", "id": "da61fd84-a361-4698-98e2-941f8084c493", "metadata": {}, "source": [ "# Clean up" ] }, { "cell_type": "code", "execution_count": null, "id": "82ee4a2d-5eb4-40ca-9e72-42dd26cd60ed", "metadata": {}, "outputs": [], "source": [ "# Run this session if you want to delete the resources you created including AWS IoT, AWS Panorama and IAM.\n", "clean_flag = input(\"Clean the created resources (y/n):\").strip()\n", "\n", "if (clean_flag == 'y'):\n", " # Clean up Panorama application\n", " panorama_test_utility.remove_application( device_id, app_id )\n", " \n", " # Clean up AWS IoT resources\n", " for CAMERA in CAMERAS:\n", " res = iot_client.update_certificate(certificateId=IOT_CERT_ID[CAMERA], newStatus='INACTIVE')\n", " res = iot_client.detach_policy(policyName=IOT_POLICY_NAME, target=IOT_CERT_ARN[CAMERA])\n", " res = iot_client.detach_thing_principal(thingName = CAMERA, principal = IOT_CERT_ARN[CAMERA])\n", " res = iot_client.delete_certificate(certificateId=IOT_CERT_ID[CAMERA])\n", " res = iot_client.delete_thing(thingName=CAMERA)\n", " \n", " # Clean up Panorama data resources\n", " try:\n", " res = panorama_client.delete_package(ForceDelete=True, PackageId=f\"packageName/{CAMERA}\")\n", " except Exception as e:\n", " print(f'Failed on Panorama delete_package :{CAMERA}')\n", " print(e)\n", " \n", " # Clean up Amazon Kinesis Video Streams resources\n", " res = kvs_client.delete_stream(\n", " StreamARN=KVS_STREAM_ARN[CAMERA]\n", " )\n", " \n", " # Clean up IAM resources\n", " res = iam_client.delete_role_policy(RoleName=IAM_ROLE_NAME, PolicyName=IAM_POLICY_NAME)\n", " res = iam_client.delete_role(RoleName=IAM_ROLE_NAME)\n", " res = iot_client.delete_role_alias(roleAlias=IOT_ROLE_ALIAS_NAME)\n", " res = iot_client.delete_policy(policyName=IOT_POLICY_NAME)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "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.9" } }, "nbformat": 4, "nbformat_minor": 5 }