From 7ac334a77947e64b36238e74e9e479d3137c1d26 Mon Sep 17 00:00:00 2001 From: Tim Schaefer Date: Tue, 5 Nov 2024 10:35:16 +0100 Subject: [PATCH] Ran linting tool --- .../handlers.py | 518 ++++++++++-------- .../handlers.py | 317 ++++++----- .../handlers.py | 289 ++++++---- 3 files changed, 674 insertions(+), 450 deletions(-) diff --git a/ProDatabase/src/redis_cloudformation_prodatabase/handlers.py b/ProDatabase/src/redis_cloudformation_prodatabase/handlers.py index fdc5392..cb703cc 100644 --- a/ProDatabase/src/redis_cloudformation_prodatabase/handlers.py +++ b/ProDatabase/src/redis_cloudformation_prodatabase/handlers.py @@ -23,99 +23,154 @@ LOG.setLevel("INFO") -#Function using urllib3 that creates and sends the API call -def HttpRequests(method, url, headers, body = None): - response = urllib3.request(method = method, url = url, body = body, headers = headers) + +# Function using urllib3 that creates and sends the API call +def HttpRequests(method, url, headers, body=None): + response = urllib3.request(method=method, url=url, body=body, headers=headers) response = response.json() return response -#Makes the POST API call for Database -def PostDatabase (base_url, event, subscription_id, http_headers): + +# Makes the POST API call for Database +def PostDatabase(base_url, event, subscription_id, http_headers): url = base_url + "/v1/subscriptions/" + str(subscription_id) + "/databases" - - response = HttpRequests(method = "POST", url = url, body = event, headers = http_headers) + + response = HttpRequests(method="POST", url=url, body=event, headers=http_headers) LOG.info(f"The POST call response is: {response}") return response -#Returns the details about all the existing Databases -def GetDatabases (base_url, subscription_id, http_headers): - url = base_url + "/v1/subscriptions/" + str(subscription_id) + "/databases?offset=0&limit=100" + +# Returns the details about all the existing Databases +def GetDatabases(base_url, subscription_id, http_headers): + url = ( + base_url + + "/v1/subscriptions/" + + str(subscription_id) + + "/databases?offset=0&limit=100" + ) LOG.info(f"URL for GetDatabases is: {url}") - response = HttpRequests(method = "GET", url = url, headers = http_headers) + response = HttpRequests(method="GET", url=url, headers=http_headers) LOG.info(f"The response after GET all Databases is: {response}") return response -#Function which returns the response of GET Database -def BasicGetDatabase (base_url, subscription_id, database_id, http_headers): - url = base_url + "/v1/subscriptions/" + str(subscription_id) + "/databases/" + str(database_id) - response = HttpRequests(method = "GET", url = url, headers = http_headers) +# Function which returns the response of GET Database +def BasicGetDatabase(base_url, subscription_id, database_id, http_headers): + url = ( + base_url + + "/v1/subscriptions/" + + str(subscription_id) + + "/databases/" + + str(database_id) + ) + + response = HttpRequests(method="GET", url=url, headers=http_headers) LOG.info(f"The response after Basic GET Database is: {response}") return response -#Function that runs a GET call based on a href link took from another response -def GetHrefLink (href_value, http_headers): - response = HttpRequests(method = "GET", url = href_value, headers = http_headers) + +# Function that runs a GET call based on a href link took from another response +def GetHrefLink(href_value, http_headers): + response = HttpRequests(method="GET", url=href_value, headers=http_headers) LOG.info(f"This is the response for the href_link given: {response}") return response -#Returns the status of the Database: active/pending/deleting -def GetDatabaseStatus (base_url, subscription_id, database_id, http_headers): - url = base_url + "/v1/subscriptions/" + str(subscription_id) + "/databases/" + str(database_id) - - response = HttpRequests(method = "GET", url = url, headers = http_headers) + +# Returns the status of the Database: active/pending/deleting +def GetDatabaseStatus(base_url, subscription_id, database_id, http_headers): + url = ( + base_url + + "/v1/subscriptions/" + + str(subscription_id) + + "/databases/" + + str(database_id) + ) + + response = HttpRequests(method="GET", url=url, headers=http_headers) LOG.info(f'Database status is: {response["status"]}') return response["status"] -#Returns the ID and the Description of the Database -def GetDatabaseId (href_value, http_headers): - response = HttpRequests(method = "GET", url = href_value, headers = http_headers) + +# Returns the ID and the Description of the Database +def GetDatabaseId(href_value, http_headers): + response = HttpRequests(method="GET", url=href_value, headers=http_headers) count = 0 - + while "databaseId" not in str(response) and count < 50: time.sleep(1) count += 1 - response = HttpRequests(method = "GET", url = href_value, headers = http_headers) + response = HttpRequests(method="GET", url=href_value, headers=http_headers) db_id = response["databaseId"] LOG.info(f"Database with ID {db_id} has the response for the GET call: {response}") return db_id -#Makes the Update API call -def PutDatabase (base_url, subscription_id, database_id, event, http_headers): - url = base_url + "/v1/subscriptions/" + str(subscription_id) + "/databases/" + str(database_id) - - response = HttpRequests(method = "PUT", url = url, body = event, headers = http_headers) + +# Makes the Update API call +def PutDatabase(base_url, subscription_id, database_id, event, http_headers): + url = ( + base_url + + "/v1/subscriptions/" + + str(subscription_id) + + "/databases/" + + str(database_id) + ) + + response = HttpRequests(method="PUT", url=url, body=event, headers=http_headers) LOG.info(f"The PUT call response is: {response}") return response -#Creates Backup for Database -def PostBackup (base_url, subscription_id, database_id, event, http_headers): - url = base_url + "/v1/subscriptions/" + str(subscription_id) + "/databases/" + str(database_id) + "/backup" - - response = HttpRequests(method = "POST", url = url, body = event, headers = http_headers) + +# Creates Backup for Database +def PostBackup(base_url, subscription_id, database_id, event, http_headers): + url = ( + base_url + + "/v1/subscriptions/" + + str(subscription_id) + + "/databases/" + + str(database_id) + + "/backup" + ) + + response = HttpRequests(method="POST", url=url, body=event, headers=http_headers) LOG.info(f"The POST call response for Create Backup is: {response}") return response -#Creates Import for Database -def PostImport (base_url, subscription_id, database_id, event, http_headers): - url = base_url + "/v1/subscriptions/" + str(subscription_id) + "/databases/" + str(database_id) + "/import" - - response = HttpRequests(method = "POST", url = url, body = event, headers = http_headers) + +# Creates Import for Database +def PostImport(base_url, subscription_id, database_id, event, http_headers): + url = ( + base_url + + "/v1/subscriptions/" + + str(subscription_id) + + "/databases/" + + str(database_id) + + "/import" + ) + + response = HttpRequests(method="POST", url=url, body=event, headers=http_headers) LOG.info(f"The POST call response for Create Import is: {response}") return response -#Makes the Delete API call -def DeleteDatabase (base_url, subscription_id, database_id, http_headers): - url = base_url + "/v1/subscriptions/" + str(subscription_id) + "/databases/" + str(database_id) - - response = HttpRequests(method = "DELETE", url = url, headers = http_headers) + +# Makes the Delete API call +def DeleteDatabase(base_url, subscription_id, database_id, http_headers): + url = ( + base_url + + "/v1/subscriptions/" + + str(subscription_id) + + "/databases/" + + str(database_id) + ) + + response = HttpRequests(method="DELETE", url=url, headers=http_headers) LOG.info("Database was deleted with response:" + str(response)) return response + @resource.handler(Action.CREATE) def create_handler( session: Optional[SessionProxy], @@ -129,25 +184,27 @@ def create_handler( resourceModel=model, ) - http_headers = {"accept":"application/json", "x-api-key":typeConfiguration.RedisAccess.xapikey, "x-api-secret-key":typeConfiguration.RedisAccess.xapisecretkey, "Content-Type":"application/json"} + http_headers = { + "accept": "application/json", + "x-api-key": typeConfiguration.RedisAccess.xapikey, + "x-api-secret-key": typeConfiguration.RedisAccess.xapisecretkey, + "Content-Type": "application/json", + } base_url = model.BaseUrl sub_id = model.SubscriptionID - #Check if we're retrying (if db_id and db_status are in callback_context) + # Check if we're retrying (if db_id and db_status are in callback_context) if "db_id" in callback_context and "db_status" in callback_context: db_id = callback_context["db_id"] db_status = callback_context["db_status"] # If the database status is complete return success if db_status == "active": - return ProgressEvent( - status=OperationStatus.SUCCESS, - resourceModel=model - ) + return ProgressEvent(status=OperationStatus.SUCCESS, resourceModel=model) elif db_status in ["failed", "error"]: return ProgressEvent.failed( HandlerErrorCode.InternalFailure, - f"Database creation failed with status: {db_status}" + f"Database creation failed with status: {db_status}", ) else: db_status = GetDatabaseStatus(base_url, sub_id, db_id, http_headers) @@ -156,92 +213,100 @@ def create_handler( status=OperationStatus.IN_PROGRESS, resourceModel=model, callbackDelaySeconds=60, - callbackContext=callback_context + callbackContext=callback_context, ) - + # Creating the event dictionary that will be identical with a Swagger API call else: event = {} - if model.DryRun != '' and model.DryRun != None: - if model.DryRun.lower() == 'true': + if model.DryRun != "" and model.DryRun != None: + if model.DryRun.lower() == "true": event["dryRun"] = True - elif model.DryRun.lower() == 'false': + elif model.DryRun.lower() == "false": event["dryRun"] = False - if model.DatabaseName != '': + if model.DatabaseName != "": event["name"] = model.DatabaseName - if model.Protocol != '': + if model.Protocol != "": event["protocol"] = model.Protocol - if model.Port != '': + if model.Port != "": event["port"] = model.Port - if model.DatasetSizeInGb != '' and model.DatasetSizeInGb != None: + if model.DatasetSizeInGb != "" and model.DatasetSizeInGb != None: event["datasetSizeInGb"] = int(model.DatasetSizeInGb) - if model.RespVersion != '': + if model.RespVersion != "": event["respVersion"] = model.RespVersion - if model.SupportOSSClusterApi != '' and model.SupportOSSClusterApi != None: - if model.SupportOSSClusterApi.lower() == 'true': + if model.SupportOSSClusterApi != "" and model.SupportOSSClusterApi != None: + if model.SupportOSSClusterApi.lower() == "true": event["supportOSSClusterApi"] = True - elif model.SupportOSSClusterApi.lower() == 'false': + elif model.SupportOSSClusterApi.lower() == "false": event["supportOSSClusterApi"] = False - if model.UseExternalEndpointForOSSClusterApi != '' and model.UseExternalEndpointForOSSClusterApi != None: - if model.UseExternalEndpointForOSSClusterApi.lower() == 'true': + if ( + model.UseExternalEndpointForOSSClusterApi != "" + and model.UseExternalEndpointForOSSClusterApi != None + ): + if model.UseExternalEndpointForOSSClusterApi.lower() == "true": event["useExternalEndpointForOSSClusterApi"] = True - elif model.UseExternalEndpointForOSSClusterApi.lower() == 'false': + elif model.UseExternalEndpointForOSSClusterApi.lower() == "false": event["useExternalEndpointForOSSClusterApi"] = False - if model.DataPersistence != '': + if model.DataPersistence != "": event["dataPersistence"] = model.DataPersistence - if model.DataEvictionPolicy != '': + if model.DataEvictionPolicy != "": event["dataEvictionPolicy"] = model.DataEvictionPolicy - if model.Replication != '' and model.Replication != None: - if model.Replication.lower() == 'true': + if model.Replication != "" and model.Replication != None: + if model.Replication.lower() == "true": event["replication"] = True - elif model.Replication.lower() == 'false': + elif model.Replication.lower() == "false": event["replication"] = False - if model.Replica != '' and model.Replica != None: + if model.Replica != "" and model.Replica != None: event["replica"] = json.loads(model.Replica) - if model.ThroughputMeasurement != '' and model.ThroughputMeasurement != None: + if model.ThroughputMeasurement != "" and model.ThroughputMeasurement != None: event["throughputMeasurement"] = json.loads(model.ThroughputMeasurement) - if model.LocalThroughputMeasurement != '' and model.LocalThroughputMeasurement != None: - event["localThroughputMeasurement"] = json.loads(model.LocalThroughputMeasurement) - if model.AverageItemSizeInBytes != '': + if ( + model.LocalThroughputMeasurement != "" + and model.LocalThroughputMeasurement != None + ): + event["localThroughputMeasurement"] = json.loads( + model.LocalThroughputMeasurement + ) + if model.AverageItemSizeInBytes != "": event["averageItemSizeInBytes"] = model.AverageItemSizeInBytes - if model.RemoteBackup != '' and model.RemoteBackup != None: + if model.RemoteBackup != "" and model.RemoteBackup != None: event["remoteBackup"] = json.loads(model.RemoteBackup) - if model.SourceIp != '': + if model.SourceIp != "": event["sourceIp"] = model.SourceIp - if model.ClientTlsCertificates != '' and model.ClientTlsCertificates != None: + if model.ClientTlsCertificates != "" and model.ClientTlsCertificates != None: event["clientTlsCertificates"] = json.loads(model.ClientTlsCertificates) - if model.EnableTls != '' and model.EnableTls != None: - if model.EnableTls.lower() == 'true': + if model.EnableTls != "" and model.EnableTls != None: + if model.EnableTls.lower() == "true": event["enableTls"] = True - elif model.EnableTls.lower() == 'false': + elif model.EnableTls.lower() == "false": event["enableTls"] = False - if model.Password != '': + if model.Password != "": event["password"] = model.Password - if model.SaslUsername != '': + if model.SaslUsername != "": event["saslUsername"] = model.SaslUsername - if model.SaslPassword != '': + if model.SaslPassword != "": event["saslPassword"] = model.SaslPassword - if model.Alerts != '' and model.Alerts != None: + if model.Alerts != "" and model.Alerts != None: event["alerts"] = json.loads(model.Alerts) - if model.Modules != '' and model.Modules != None: + if model.Modules != "" and model.Modules != None: event["modules"] = json.loads(model.Modules) - if model.QueryPerformanceFactor != '': + if model.QueryPerformanceFactor != "": event["queryPerformanceFactor"] = model.QueryPerformanceFactor event = json.dumps(event) LOG.info(f"The actual event sent for POST call is: {event}") - #Sending a POST API call to create a database + # Sending a POST API call to create a database response = PostDatabase(base_url, event, sub_id, http_headers) while response["status"] != "processing-completed": response = HttpRequests("GET", response["links"][0]["href"], http_headers) - #Retrieving the detailed link for Database after POST call + # Retrieving the detailed link for Database after POST call LOG.info(f'This is the link after POST call {response["links"][0]["href"]}') - #Retrieving Database ID and it's Description - db_id = GetDatabaseId (response["links"][0]["href"], http_headers) + # Retrieving Database ID and it's Description + db_id = GetDatabaseId(response["links"][0]["href"], http_headers) db_id = str(db_id) model.DatabaseID = db_id @@ -256,9 +321,10 @@ def create_handler( status=OperationStatus.IN_PROGRESS, resourceModel=model, callbackDelaySeconds=60, - callbackContext=callback_context + callbackContext=callback_context, ) + @resource.handler(Action.UPDATE) def update_handler( session: Optional[SessionProxy], @@ -272,104 +338,113 @@ def update_handler( resourceModel=model, ) - http_headers = {"accept":"application/json", "x-api-key":typeConfiguration.RedisAccess.xapikey, "x-api-secret-key":typeConfiguration.RedisAccess.xapisecretkey, "Content-Type":"application/json"} + http_headers = { + "accept": "application/json", + "x-api-key": typeConfiguration.RedisAccess.xapikey, + "x-api-secret-key": typeConfiguration.RedisAccess.xapisecretkey, + "Content-Type": "application/json", + } base_url = model.BaseUrl sub_id = model.SubscriptionID db_id = model.DatabaseID - #Checking if the Update should create a new backup on demand + # Checking if the Update should create a new backup on demand event = {} - if model.OnDemandBackup == 'true' or model.OnDemandBackup == 'True': - if model.RegionName == 'null' or model.RegionName == '': - event['regionName'] = None + if model.OnDemandBackup == "true" or model.OnDemandBackup == "True": + if model.RegionName == "null" or model.RegionName == "": + event["regionName"] = None event = json.dumps(event) LOG.info(f"The event when regionName is null is: {event}") else: - event['regionName'] = model.RegionName + event["regionName"] = model.RegionName event = json.dumps(event) - response = PostBackup (base_url, sub_id, db_id, event, http_headers) - - #Checking if the Update should Import a previous created Backup - elif model.OnDemandImport == 'true' or model.OnDemandImport == 'True': - if model.SourceType != '': + response = PostBackup(base_url, sub_id, db_id, event, http_headers) + + # Checking if the Update should Import a previous created Backup + elif model.OnDemandImport == "true" or model.OnDemandImport == "True": + if model.SourceType != "": event["sourceType"] = model.SourceType - if model.ImportFromUri != '': + if model.ImportFromUri != "": importFromUriList = [] importFromUriList.append(model.ImportFromUri) event["importFromUri"] = importFromUriList - event = json.dumps(event) + event = json.dumps(event) LOG.info(f"The event sent for Import is: {event}") - response = PostImport (base_url, sub_id, db_id, event, http_headers) + response = PostImport(base_url, sub_id, db_id, event, http_headers) - #If neither Backup nor Import are desired upon Update call, then initiate a normal Put call for the current database + # If neither Backup nor Import are desired upon Update call, then initiate a normal Put call for the current database else: - if model.DryRun != '' and model.DryRun != None: - if model.DryRun.lower() == 'true': + if model.DryRun != "" and model.DryRun != None: + if model.DryRun.lower() == "true": event["dryRun"] = True - elif model.DryRun.lower() == 'false': + elif model.DryRun.lower() == "false": event["dryRun"] = False - if model.DatabaseName != '': + if model.DatabaseName != "": event["name"] = model.DatabaseName - if model.DatasetSizeInGb != '' and model.DatasetSizeInGb != None: + if model.DatasetSizeInGb != "" and model.DatasetSizeInGb != None: event["datasetSizeInGb"] = int(model.DatasetSizeInGb) - if model.RespVersion != '': + if model.RespVersion != "": event["respVersion"] = model.RespVersion - if model.ThroughputMeasurement != '' and model.ThroughputMeasurement != None: - event["throughputMeasurement"] = json.loads(model.ThroughputMeasurement) - if model.DataPersistence != '': + if model.ThroughputMeasurement != "" and model.ThroughputMeasurement != None: + event["throughputMeasurement"] = json.loads(model.ThroughputMeasurement) + if model.DataPersistence != "": event["dataPersistence"] = model.DataPersistence - if model.DataEvictionPolicy != '': + if model.DataEvictionPolicy != "": event["dataEvictionPolicy"] = model.DataEvictionPolicy - if model.Replication != '' and model.Replication != None: - if model.Replication.lower() == 'true': + if model.Replication != "" and model.Replication != None: + if model.Replication.lower() == "true": event["replication"] = True - elif model.Replication.lower() == 'false': + elif model.Replication.lower() == "false": event["replication"] = False - if model.RegexRules != '': + if model.RegexRules != "": event["regexRules"] = model.RegexRules - if model.Replica != '' and model.Replica != None: + if model.Replica != "" and model.Replica != None: event["replica"] = json.loads(model.Replica) - if model.SupportOSSClusterApi != '' and model.SupportOSSClusterApi != None: - if model.SupportOSSClusterApi.lower() == 'true': + if model.SupportOSSClusterApi != "" and model.SupportOSSClusterApi != None: + if model.SupportOSSClusterApi.lower() == "true": event["supportOSSClusterApi"] = True - elif model.SupportOSSClusterApi.lower() == 'false': + elif model.SupportOSSClusterApi.lower() == "false": event["supportOSSClusterApi"] = False - if model.UseExternalEndpointForOSSClusterApi != '' and model.UseExternalEndpointForOSSClusterApi != None: - if model.UseExternalEndpointForOSSClusterApi.lower() == 'true': + if ( + model.UseExternalEndpointForOSSClusterApi != "" + and model.UseExternalEndpointForOSSClusterApi != None + ): + if model.UseExternalEndpointForOSSClusterApi.lower() == "true": event["useExternalEndpointForOSSClusterApi"] = True - elif model.UseExternalEndpointForOSSClusterApi.lower() == 'false': + elif model.UseExternalEndpointForOSSClusterApi.lower() == "false": event["useExternalEndpointForOSSClusterApi"] = False - if model.Password != '': + if model.Password != "": event["password"] = model.Password - if model.SaslUsername != '': + if model.SaslUsername != "": event["saslUsername"] = model.SaslUsername - if model.SaslPassword != '': + if model.SaslPassword != "": event["saslPassword"] = model.SaslPassword - if model.SourceIp != '': + if model.SourceIp != "": event["sourceIp"] = model.SourceIp - if model.ClientTlsCertificates != '' and model.ClientTlsCertificates != None: + if model.ClientTlsCertificates != "" and model.ClientTlsCertificates != None: event["clientTlsCertificates"] = json.loads(model.ClientTlsCertificates) - if model.EnableTls != '' and model.EnableTls != None: - if model.EnableTls.lower() == 'true': + if model.EnableTls != "" and model.EnableTls != None: + if model.EnableTls.lower() == "true": event["enableTls"] = True - elif model.EnableTls.lower() == 'false': + elif model.EnableTls.lower() == "false": event["enableTls"] = False - if model.EnableDefaultUser != '': + if model.EnableDefaultUser != "": event["enableDefaultUser"] = model.EnableDefaultUser - if model.RemoteBackup != '' and model.RemoteBackup != None: + if model.RemoteBackup != "" and model.RemoteBackup != None: event["remoteBackup"] = json.loads(model.RemoteBackup) - if model.Alerts != '' and model.Alerts != None: + if model.Alerts != "" and model.Alerts != None: event["alerts"] = json.loads(model.Alerts) - if model.QueryPerformanceFactor != '': + if model.QueryPerformanceFactor != "": event["queryPerformanceFactor"] = model.QueryPerformanceFactor event = json.dumps(event) LOG.info(f"The event sent for PUT call is: {event}") response = PutDatabase(base_url, sub_id, db_id, event, http_headers) LOG.info(f"Response for PUT call is: {response}") - + return read_handler(session, request, callback_context) + @resource.handler(Action.DELETE) def delete_handler( session: Optional[SessionProxy], @@ -383,65 +458,62 @@ def delete_handler( resourceModel=model, ) - http_headers = {"accept":"application/json", "x-api-key":typeConfiguration.RedisAccess.xapikey, "x-api-secret-key":typeConfiguration.RedisAccess.xapisecretkey, "Content-Type":"application/json"} + http_headers = { + "accept": "application/json", + "x-api-key": typeConfiguration.RedisAccess.xapikey, + "x-api-secret-key": typeConfiguration.RedisAccess.xapisecretkey, + "Content-Type": "application/json", + } base_url = model.BaseUrl sub_id = model.SubscriptionID db_id = model.DatabaseID - #This block runs only after callback is activated in order to check the database status + # This block runs only after callback is activated in order to check the database status if callback_context.get("delete_in_progress"): try: # Poll the Database status response_check = BasicGetDatabase(base_url, sub_id, db_id, http_headers) LOG.info(f"Polling deletion status: {response_check}") - if 'delete-draft' in response_check: + if "delete-draft" in response_check: return ProgressEvent( status=OperationStatus.IN_PROGRESS, resourceModel=model, callbackDelaySeconds=60, # Poll every 60 seconds - callbackContext={"delete_in_progress": True} - ) - elif "Not Found" in str(response_check): - return ProgressEvent( - status=OperationStatus.SUCCESS + callbackContext={"delete_in_progress": True}, ) + elif "Not Found" in str(response_check): + return ProgressEvent(status=OperationStatus.SUCCESS) else: LOG.info(f"Database has the status: {response_check['status']}") return ProgressEvent.failed( HandlerErrorCode.InternalFailure, - f"Database {db_id} still exists and is not in a deleting state." + f"Database {db_id} still exists and is not in a deleting state.", ) except Exception as e: - return ProgressEvent.failed( - HandlerErrorCode.InternalFailure, - str(e) - ) + return ProgressEvent.failed(HandlerErrorCode.InternalFailure, str(e)) try: - DeleteDatabase (base_url, sub_id, db_id, http_headers) + DeleteDatabase(base_url, sub_id, db_id, http_headers) response_check = BasicGetDatabase(base_url, sub_id, db_id, http_headers) # Handle the case where the database is already not found if "404" in str(response_check): return ProgressEvent.failed( - HandlerErrorCode.NotFound, - f"Database with ID {db_id} was not found." - ) + HandlerErrorCode.NotFound, f"Database with ID {db_id} was not found." + ) else: return ProgressEvent( status=OperationStatus.IN_PROGRESS, resourceModel=model, callbackDelaySeconds=60, # Poll every 60 seconds - callbackContext={"delete_in_progress": True} + callbackContext={"delete_in_progress": True}, ) except Exception as e: - return ProgressEvent.failed( - HandlerErrorCode.InternalFailure, - str(e) - ) + return ProgressEvent.failed(HandlerErrorCode.InternalFailure, str(e)) + @resource.handler(Action.READ) def read_handler( @@ -452,7 +524,12 @@ def read_handler( model = request.desiredResourceState typeConfiguration = request.typeConfiguration - http_headers = {"accept":"application/json", "x-api-key":typeConfiguration.RedisAccess.xapikey, "x-api-secret-key":typeConfiguration.RedisAccess.xapisecretkey, "Content-Type":"application/json"} + http_headers = { + "accept": "application/json", + "x-api-key": typeConfiguration.RedisAccess.xapikey, + "x-api-secret-key": typeConfiguration.RedisAccess.xapisecretkey, + "Content-Type": "application/json", + } base_url = model.BaseUrl sub_id = model.SubscriptionID db_id = model.DatabaseID @@ -463,16 +540,14 @@ def read_handler( LOG.info(f"This is the response after BasicGetDatabase: {response}") # If the resource does not exist anymore, return NotFound - if 'databaseId' not in str(response) or response['databaseId'] != int(db_id): + if "databaseId" not in str(response) or response["databaseId"] != int(db_id): LOG.info(f"Database with ID {db_id} not found. Returning NotFound error.") return ProgressEvent.failed( - HandlerErrorCode.NotFound, - f"Database {db_id} does not exist." + HandlerErrorCode.NotFound, f"Database {db_id} does not exist." ) elif response["status"] == "deleting": return ProgressEvent.failed( - HandlerErrorCode.NotFound, - f"Database {db_id} is in delete state" + HandlerErrorCode.NotFound, f"Database {db_id} is in delete state" ) else: # If the resource still exists, return it @@ -483,6 +558,7 @@ def read_handler( resourceModel=model, ) + @resource.handler(Action.LIST) def list_handler( session: Optional[SessionProxy], @@ -492,7 +568,12 @@ def list_handler( model = request.desiredResourceState typeConfiguration = request.typeConfiguration - http_headers = {"accept":"application/json", "x-api-key":typeConfiguration.RedisAccess.xapikey, "x-api-secret-key":typeConfiguration.RedisAccess.xapisecretkey, "Content-Type":"application/json"} + http_headers = { + "accept": "application/json", + "x-api-key": typeConfiguration.RedisAccess.xapikey, + "x-api-secret-key": typeConfiguration.RedisAccess.xapisecretkey, + "Content-Type": "application/json", + } base_url = model.BaseUrl sub_id = model.SubscriptionID @@ -513,44 +594,52 @@ def list_handler( LOG.info(f"Retrieved databases: {databases}") for db in databases: # Only include databases that are not in 'deleting' state - if 'delete-draft' not in str(db): - models.append(ResourceModel( - DatabaseID=str(db.get("databaseId")), - BaseUrl=base_url, - SubscriptionID=sub_id, - DryRun="false", - DatabaseName=db.get("name"), - Protocol=db.get("protocol"), - Port=db.get("port"), - DatasetSizeInGb=db.get("datasetSizeInGb"), - RespVersion=db.get("respVersion"), - SupportOSSClusterApi=db.get("supportOSSClusterApi"), - UseExternalEndpointForOSSClusterApi=db.get("useExternalEndpointForOSSClusterApi"), - DataPersistence=db.get("dataPersistence"), - DataEvictionPolicy=db.get("dataEvictionPolicy"), - Replication=db.get("replication"), - Replica=db.get("replica"), - ThroughputMeasurement=db.get("throughputMeasurement"), - LocalThroughputMeasurement=db.get("localThroughputMeasurement"), - AverageItemSizeInBytes=db.get("averageItemSizeInBytes"), - RemoteBackup=db.get("backup"), - SourceIp=db.get("security", {}).get("sourceIps", []), - ClientTlsCertificates=db.get("security", {}).get("tlsClientAuthentication", None), - EnableTls=db.get("security", {}).get("enableTls", None), - Password=db.get("password", None), - SaslUsername=db.get("saslUsername", None), - SaslPassword=db.get("saslPassword", None), - Alerts=db.get("alerts"), - Modules=db.get("modules"), - QueryPerformanceFactor=db.get("queryPerformanceFactor", None), - RegexRules=db.get("clustering", {}).get("regexRules", []), - EnableDefaultUser=db.get("security", {}).get("enableDefaultUser", None), - OnDemandBackup="", - RegionName="", - OnDemandImport="", - SourceType="", - ImportFromUri="", - )) + if "delete-draft" not in str(db): + models.append( + ResourceModel( + DatabaseID=str(db.get("databaseId")), + BaseUrl=base_url, + SubscriptionID=sub_id, + DryRun="false", + DatabaseName=db.get("name"), + Protocol=db.get("protocol"), + Port=db.get("port"), + DatasetSizeInGb=db.get("datasetSizeInGb"), + RespVersion=db.get("respVersion"), + SupportOSSClusterApi=db.get("supportOSSClusterApi"), + UseExternalEndpointForOSSClusterApi=db.get( + "useExternalEndpointForOSSClusterApi" + ), + DataPersistence=db.get("dataPersistence"), + DataEvictionPolicy=db.get("dataEvictionPolicy"), + Replication=db.get("replication"), + Replica=db.get("replica"), + ThroughputMeasurement=db.get("throughputMeasurement"), + LocalThroughputMeasurement=db.get("localThroughputMeasurement"), + AverageItemSizeInBytes=db.get("averageItemSizeInBytes"), + RemoteBackup=db.get("backup"), + SourceIp=db.get("security", {}).get("sourceIps", []), + ClientTlsCertificates=db.get("security", {}).get( + "tlsClientAuthentication", None + ), + EnableTls=db.get("security", {}).get("enableTls", None), + Password=db.get("password", None), + SaslUsername=db.get("saslUsername", None), + SaslPassword=db.get("saslPassword", None), + Alerts=db.get("alerts"), + Modules=db.get("modules"), + QueryPerformanceFactor=db.get("queryPerformanceFactor", None), + RegexRules=db.get("clustering", {}).get("regexRules", []), + EnableDefaultUser=db.get("security", {}).get( + "enableDefaultUser", None + ), + OnDemandBackup="", + RegionName="", + OnDemandImport="", + SourceType="", + ImportFromUri="", + ) + ) # If no subscriptions are found, return an empty model array LOG.info(f"Final list of models: {models}") @@ -558,4 +647,3 @@ def list_handler( status=OperationStatus.SUCCESS, resourceModels=models, ) - \ No newline at end of file diff --git a/ProSubscription/src/redis_cloudformation_prosubscription/handlers.py b/ProSubscription/src/redis_cloudformation_prosubscription/handlers.py index c86fda6..213f6c7 100644 --- a/ProSubscription/src/redis_cloudformation_prosubscription/handlers.py +++ b/ProSubscription/src/redis_cloudformation_prosubscription/handlers.py @@ -23,110 +23,148 @@ LOG.setLevel("INFO") -#Function using urllib3 that creates and sends the API call -def HttpRequests(method, url, headers, body = None): - response = urllib3.request(method = method, url = url, body = body, headers = headers) + +# Function using urllib3 that creates and sends the API call +def HttpRequests(method, url, headers, body=None): + response = urllib3.request(method=method, url=url, body=body, headers=headers) response = response.json() return response -#Function to Create a new Subscription -def PostSubscription (base_url, event, http_headers): + +# Function to Create a new Subscription +def PostSubscription(base_url, event, http_headers): url = base_url + "/v1/subscriptions/" - - response = HttpRequests(method = "POST", url = url, body = event, headers = http_headers) + + response = HttpRequests(method="POST", url=url, body=event, headers=http_headers) LOG.info(f"The POST call response is: {response}") return response -#Returns the details about all the existing Subscriptions -def GetSubscriptions (base_url, http_headers): + +# Returns the details about all the existing Subscriptions +def GetSubscriptions(base_url, http_headers): url = base_url + "/v1/subscriptions" - response = HttpRequests(method = "GET", url = url, headers = http_headers) + response = HttpRequests(method="GET", url=url, headers=http_headers) LOG.info(f"The response after GET all Subscriptions is: {response}") return response -#Function which returns the response of GET Subscription -def BasicGetSubscription (base_url, subscription_id, http_headers): + +# Function which returns the response of GET Subscription +def BasicGetSubscription(base_url, subscription_id, http_headers): url = base_url + "/v1/subscriptions/" + str(subscription_id) - response = HttpRequests(method = "GET", url = url, headers = http_headers) + response = HttpRequests(method="GET", url=url, headers=http_headers) LOG.info(f"The response after basic GET Subscription is: {response}") return response -#Function that runs a GET call based on a href link took from another response -def GetHrefLink (href_value, http_headers): - response = HttpRequests(method = "GET", url = href_value, headers = http_headers) + +# Function that runs a GET call based on a href link took from another response +def GetHrefLink(href_value, http_headers): + response = HttpRequests(method="GET", url=href_value, headers=http_headers) LOG.info(f"This is the response for the href_link given: {response}") return response -#Function to retrieve Subscription ID and it's Description -def GetSubscriptionId (href_value, http_headers): - response = HttpRequests(method = "GET", url = href_value, headers = http_headers) + +# Function to retrieve Subscription ID and it's Description +def GetSubscriptionId(href_value, http_headers): + response = HttpRequests(method="GET", url=href_value, headers=http_headers) count = 0 - + while "resourceId" not in str(response) and count < 50: time.sleep(1) count += 1 - response = HttpRequests(method = "GET", url = href_value, headers = http_headers) + response = HttpRequests(method="GET", url=href_value, headers=http_headers) sub_id = response["response"]["resourceId"] sub_description = response["description"] - LOG.info(f"Subscription with ID {sub_id} has the response for the GET call: {response}") + LOG.info( + f"Subscription with ID {sub_id} has the response for the GET call: {response}" + ) return sub_id, sub_description -#Function to retrieve subscription status to then call the callback_context -def GetSubscriptionStatus (base_url, subscription_id, http_headers): + +# Function to retrieve subscription status to then call the callback_context +def GetSubscriptionStatus(base_url, subscription_id, http_headers): sub_url = base_url + "/v1/subscriptions/" + str(subscription_id) - - response = HttpRequests(method = "GET", url = sub_url, headers = http_headers) + + response = HttpRequests(method="GET", url=sub_url, headers=http_headers) sub_status = response["status"] LOG.info("Subscription status is: " + sub_status) return sub_status -#Function which returns the Database ID -def GetDatabaseId (base_url, subscription_id, http_headers, offset = 0, limit = 100): - db_url = base_url + "/v1/subscriptions/" + str(subscription_id) + "/databases?offset=" + str(offset) + "&limit=" + str(limit) - - response = HttpRequests(method = "GET", url = db_url, headers = http_headers) + +# Function which returns the Database ID +def GetDatabaseId(base_url, subscription_id, http_headers, offset=0, limit=100): + db_url = ( + base_url + + "/v1/subscriptions/" + + str(subscription_id) + + "/databases?offset=" + + str(offset) + + "&limit=" + + str(limit) + ) + + response = HttpRequests(method="GET", url=db_url, headers=http_headers) default_db_id = response["subscription"][0]["databases"][0]["databaseId"] return default_db_id -#Makes the Update API call -def PutSubscription (base_url, subscription_id, event, http_headers): + +# Makes the Update API call +def PutSubscription(base_url, subscription_id, event, http_headers): url = base_url + "/v1/subscriptions/" + subscription_id - - response = HttpRequests(method = "PUT", url = url, body = event, headers = http_headers) + + response = HttpRequests(method="PUT", url=url, body=event, headers=http_headers) LOG.info(f"The PUT call response is: {response}") return response -#Makes the Delete API call -def DeleteDatabase (base_url, subscription_id, database_id, http_headers): - url = base_url + "/v1/subscriptions/" + str(subscription_id) + "/databases/" + str(database_id) - - response = HttpRequests(method = "DELETE", url = url, headers = http_headers) + +# Makes the Delete API call +def DeleteDatabase(base_url, subscription_id, database_id, http_headers): + url = ( + base_url + + "/v1/subscriptions/" + + str(subscription_id) + + "/databases/" + + str(database_id) + ) + + response = HttpRequests(method="DELETE", url=url, headers=http_headers) LOG.info("Default database was deleted with response:" + str(response)) -#Functions which returns the total number of databases assigned to a subscription -def GetNumberOfDatabases (base_url, subscription_id, http_headers): + +# Functions which returns the total number of databases assigned to a subscription +def GetNumberOfDatabases(base_url, subscription_id, http_headers): sub_url = base_url + "/v1/subscriptions/" + str(subscription_id) - response = HttpRequests(method = "GET", url = sub_url, headers = http_headers) + response = HttpRequests(method="GET", url=sub_url, headers=http_headers) db_number = response["numberOfDatabases"] - LOG.info("The Number of Databases assigned to Subscription with ID " + str(subscription_id) + " is " + str(db_number)) + LOG.info( + "The Number of Databases assigned to Subscription with ID " + + str(subscription_id) + + " is " + + str(db_number) + ) return str(db_number) -#Function to delete a subscription -def DeleteSubscription (base_url, subscription_id, http_headers): + +# Function to delete a subscription +def DeleteSubscription(base_url, subscription_id, http_headers): subs_url = base_url + "/v1/subscriptions/" + subscription_id - - response = HttpRequests(method = "DELETE", url = subs_url, headers = http_headers) - time.sleep(10) #hardcoded 10 seconds of sleep because the delete call takes longer to be processed. - LOG.info(f"Subscription with ID {subscription_id} was deleted with response: {response}") + + response = HttpRequests(method="DELETE", url=subs_url, headers=http_headers) + time.sleep( + 10 + ) # hardcoded 10 seconds of sleep because the delete call takes longer to be processed. + LOG.info( + f"Subscription with ID {subscription_id} was deleted with response: {response}" + ) return response + @resource.handler(Action.CREATE) def create_handler( session: Optional[SessionProxy], @@ -140,10 +178,15 @@ def create_handler( resourceModel=model, ) - http_headers = {"accept":"application/json", "x-api-key":typeConfiguration.RedisAccess.xapikey, "x-api-secret-key":typeConfiguration.RedisAccess.xapisecretkey, "Content-Type":"application/json"} + http_headers = { + "accept": "application/json", + "x-api-key": typeConfiguration.RedisAccess.xapikey, + "x-api-secret-key": typeConfiguration.RedisAccess.xapisecretkey, + "Content-Type": "application/json", + } base_url = model.BaseUrl - #Check if we're retrying (if sub_id and sub_status are in callback_context) + # Check if we're retrying (if sub_id and sub_status are in callback_context) if "sub_id" in callback_context and "sub_status" in callback_context: sub_id = callback_context["sub_id"] sub_status = callback_context["sub_status"] @@ -159,24 +202,23 @@ def create_handler( except: return ProgressEvent.failed( HandlerErrorCode.InternalFailure, - f"The deletion for the Dummy Database with ID: {default_db_id} has failed. Please delete it manually." - ) + f"The deletion for the Dummy Database with ID: {default_db_id} has failed. Please delete it manually.", + ) if GetNumberOfDatabases(base_url, sub_id, http_headers) == "0": return ProgressEvent( - status=OperationStatus.SUCCESS, - resourceModel=model + status=OperationStatus.SUCCESS, resourceModel=model ) else: return ProgressEvent( status=OperationStatus.IN_PROGRESS, resourceModel=model, callbackDelaySeconds=60, - callbackContext=callback_context + callbackContext=callback_context, ) elif sub_status in ["failed", "error"]: return ProgressEvent.failed( HandlerErrorCode.InternalFailure, - f"Subscription creation failed with status: {sub_status}. Please manually check that all resources have been deleted from Redis Cloud console." + f"Subscription creation failed with status: {sub_status}. Please manually check that all resources have been deleted from Redis Cloud console.", ) else: sub_status = GetSubscriptionStatus(base_url, sub_id, http_headers) @@ -185,56 +227,56 @@ def create_handler( status=OperationStatus.IN_PROGRESS, resourceModel=model, callbackDelaySeconds=60, - callbackContext=callback_context + callbackContext=callback_context, ) # If we're here, it means this is the first call (no sub_id in callback_context) # Creating the event dictionary that will be identical with a Swagger API call else: databasesList = [] - databasesDict = {} + databasesDict = {} databasesDict["name"] = "DummyDatabase" databasesDict["datasetSizeInGb"] = 1 databasesList.append(databasesDict) - + event = {} - if model.SubscriptionName != '': + if model.SubscriptionName != "": event["name"] = model.SubscriptionName - if model.DryRun != '': - if model.DryRun.lower() == 'true': + if model.DryRun != "": + if model.DryRun.lower() == "true": event["dryRun"] = True - elif model.DryRun.lower() == 'false': + elif model.DryRun.lower() == "false": event["dryRun"] = False - if model.DeploymentType != '': + if model.DeploymentType != "": event["deploymentType"] = model.DeploymentType - if model.PaymentMethod != '': + if model.PaymentMethod != "": event["paymentMethod"] = model.PaymentMethod - if model.PaymentMethodId != '': + if model.PaymentMethodId != "": event["paymentMethodId"] = int(model.PaymentMethodId) - if model.MemoryStorage != '': + if model.MemoryStorage != "": event["memoryStorage"] = model.MemoryStorage event["cloudProviders"] = json.loads(model.CloudProviders) event["databases"] = databasesList - if model.RedisVersion != '': + if model.RedisVersion != "": event["redisVersion"] = model.RedisVersion event = json.dumps(event) LOG.info(f"The actual event sent for POST call is: {event}") - #Sending a POST API call to create a subscription and a dummy database + # Sending a POST API call to create a subscription and a dummy database response = PostSubscription(base_url, event, http_headers) - #Retrieving the detailed link for Subscription after POST call + # Retrieving the detailed link for Subscription after POST call href_value = response["links"][0]["href"] - - #Retrieving Subscription ID and it's Description - sub_id, sub_description = GetSubscriptionId (href_value, http_headers) + + # Retrieving Subscription ID and it's Description + sub_id, sub_description = GetSubscriptionId(href_value, http_headers) sub_id = str(sub_id) model.SubscriptionID = sub_id LOG.info(f"The Subscription ID is: {sub_id}") LOG.info(f"The Subscription description is: {sub_description}") - + # Initial status check and storing both in callback_context sub_status = GetSubscriptionStatus(base_url, sub_id, http_headers) callback_context["sub_id"] = sub_id @@ -244,9 +286,10 @@ def create_handler( status=OperationStatus.IN_PROGRESS, resourceModel=model, callbackDelaySeconds=60, - callbackContext=callback_context + callbackContext=callback_context, ) + @resource.handler(Action.UPDATE) def update_handler( session: Optional[SessionProxy], @@ -259,23 +302,29 @@ def update_handler( status=OperationStatus.IN_PROGRESS, resourceModel=model, ) - http_headers = {"accept":"application/json", "x-api-key":typeConfiguration.RedisAccess.xapikey, "x-api-secret-key":typeConfiguration.RedisAccess.xapisecretkey, "Content-Type":"application/json"} + http_headers = { + "accept": "application/json", + "x-api-key": typeConfiguration.RedisAccess.xapikey, + "x-api-secret-key": typeConfiguration.RedisAccess.xapisecretkey, + "Content-Type": "application/json", + } base_url = model.BaseUrl sub_id = model.SubscriptionID - #Decided that only the Subscription Name is an updatable field + # Decided that only the Subscription Name is an updatable field event = {} - if model.SubscriptionName != '': + if model.SubscriptionName != "": event["name"] = model.SubscriptionName event = json.dumps(event) LOG.info(f"The event sent for PUT call is: {event}") - PutSubscription (base_url, sub_id, event, http_headers) + PutSubscription(base_url, sub_id, event, http_headers) else: LOG.info(f"No Updates required.") return read_handler(session, request, callback_context) return read_handler(session, request, callback_context) + @resource.handler(Action.DELETE) def delete_handler( session: Optional[SessionProxy], @@ -289,7 +338,12 @@ def delete_handler( resourceModel=model, ) - http_headers = {"accept": "application/json", "x-api-key": typeConfiguration.RedisAccess.xapikey, "x-api-secret-key": typeConfiguration.RedisAccess.xapisecretkey, "Content-Type": "application/json"} + http_headers = { + "accept": "application/json", + "x-api-key": typeConfiguration.RedisAccess.xapikey, + "x-api-secret-key": typeConfiguration.RedisAccess.xapisecretkey, + "Content-Type": "application/json", + } base_url = model.BaseUrl sub_id = model.SubscriptionID @@ -300,24 +354,19 @@ def delete_handler( response_check = BasicGetSubscription(base_url, sub_id, http_headers) LOG.info(f"Polling deletion status: {response_check}") - if response_check['status'] == 'deleting': + if response_check["status"] == "deleting": return ProgressEvent( status=OperationStatus.IN_PROGRESS, resourceModel=model, callbackDelaySeconds=60, # Poll every 60 seconds - callbackContext={"delete_in_progress": True} - ) - - if "Not Found" in str(response_check): - return ProgressEvent( - status=OperationStatus.SUCCESS + callbackContext={"delete_in_progress": True}, ) + if "Not Found" in str(response_check): + return ProgressEvent(status=OperationStatus.SUCCESS) + except Exception as e: - return ProgressEvent.failed( - HandlerErrorCode.InternalFailure, - str(e) - ) + return ProgressEvent.failed(HandlerErrorCode.InternalFailure, str(e)) # If this is the first attempt to delete try: @@ -326,38 +375,42 @@ def delete_handler( href_value = delete_response["links"][0]["href"] LOG.info(f"Deletion initiated: {delete_response}") - delete_response = GetHrefLink (href_value, http_headers) + delete_response = GetHrefLink(href_value, http_headers) # Handle the case where the subscription is already not found - if 'response' in str(delete_response) and 'error' in str(delete_response['response']): - error_code = str(delete_response['response']['error']['type']) - if error_code == 'SUBSCRIPTION_NOT_FOUND': + if "response" in str(delete_response) and "error" in str( + delete_response["response"] + ): + error_code = str(delete_response["response"]["error"]["type"]) + if error_code == "SUBSCRIPTION_NOT_FOUND": return ProgressEvent.failed( HandlerErrorCode.NotFound, - f"Subscription with ID {sub_id} was not found." - ) + f"Subscription with ID {sub_id} was not found.", + ) else: response_check = BasicGetSubscription(base_url, sub_id, http_headers) - if 'id' in str(response_check) and response_check['id'] == int(sub_id) and response_check['status'] == 'deleting': + if ( + "id" in str(response_check) + and response_check["id"] == int(sub_id) + and response_check["status"] == "deleting" + ): LOG.info(f"Subscription has the status: deleting.") return ProgressEvent( status=OperationStatus.IN_PROGRESS, resourceModel=model, callbackDelaySeconds=60, # Poll every 60 seconds - callbackContext={"delete_in_progress": True} + callbackContext={"delete_in_progress": True}, ) else: LOG.info(f"Subscription has the status: {response_check['status']}") return ProgressEvent.failed( HandlerErrorCode.InternalFailure, - f"Subscription {sub_id} still exists and is not in a deleting state." + f"Subscription {sub_id} still exists and is not in a deleting state.", ) except Exception as e: - return ProgressEvent.failed( - HandlerErrorCode.InternalFailure, - str(e) - ) + return ProgressEvent.failed(HandlerErrorCode.InternalFailure, str(e)) + @resource.handler(Action.READ) def read_handler( @@ -368,7 +421,12 @@ def read_handler( model = request.desiredResourceState typeConfiguration = request.typeConfiguration - http_headers = {"accept":"application/json", "x-api-key":typeConfiguration.RedisAccess.xapikey, "x-api-secret-key":typeConfiguration.RedisAccess.xapisecretkey, "Content-Type":"application/json"} + http_headers = { + "accept": "application/json", + "x-api-key": typeConfiguration.RedisAccess.xapikey, + "x-api-secret-key": typeConfiguration.RedisAccess.xapisecretkey, + "Content-Type": "application/json", + } base_url = model.BaseUrl sub_id = model.SubscriptionID LOG.info(f"This is the model sent in read handler: {model}") @@ -378,16 +436,14 @@ def read_handler( LOG.info(f"This is the response after BasicGetSubscription: {response}") # If the resource does not exist anymore, return NotFound - if 'id' not in str(response) or response['id'] != int(sub_id): + if "id" not in str(response) or response["id"] != int(sub_id): LOG.info(f"Subscription with ID {sub_id} not found. Returning NotFound error.") return ProgressEvent.failed( - HandlerErrorCode.NotFound, - f"Subscription {sub_id} does not exist." + HandlerErrorCode.NotFound, f"Subscription {sub_id} does not exist." ) elif response["status"] == "deleting": return ProgressEvent.failed( - HandlerErrorCode.NotFound, - f"Subscription {sub_id} is in delete state" + HandlerErrorCode.NotFound, f"Subscription {sub_id} is in delete state" ) else: # If the resource still exists, return it @@ -398,6 +454,7 @@ def read_handler( resourceModel=model, ) + @resource.handler(Action.LIST) def list_handler( session: Optional[SessionProxy], @@ -407,7 +464,12 @@ def list_handler( model = request.desiredResourceState typeConfiguration = request.typeConfiguration - http_headers = {"accept":"application/json", "x-api-key":typeConfiguration.RedisAccess.xapikey, "x-api-secret-key":typeConfiguration.RedisAccess.xapisecretkey, "Content-Type":"application/json"} + http_headers = { + "accept": "application/json", + "x-api-key": typeConfiguration.RedisAccess.xapikey, + "x-api-secret-key": typeConfiguration.RedisAccess.xapisecretkey, + "Content-Type": "application/json", + } base_url = model.BaseUrl # Fetch all subscriptions from the external service @@ -419,19 +481,21 @@ def list_handler( LOG.info(f"Retrieved subscriptions: {subscriptions}") for sub in subscriptions: # Only include subscriptions that are not in 'deleting' state - if sub['status'] != 'deleting': - models.append(ResourceModel( - SubscriptionID=str(sub.get("id")), - BaseUrl=base_url, - SubscriptionName=sub.get("name"), - DryRun="false", - DeploymentType=sub.get("deploymentType"), - PaymentMethod=sub.get("paymentMethodType"), - PaymentMethodId=sub.get("paymentMethodId"), - MemoryStorage=sub.get("memoryStorage"), - CloudProviders=sub.get("cloudDetails"), - RedisVersion=model.RedisVersion, - )) + if sub["status"] != "deleting": + models.append( + ResourceModel( + SubscriptionID=str(sub.get("id")), + BaseUrl=base_url, + SubscriptionName=sub.get("name"), + DryRun="false", + DeploymentType=sub.get("deploymentType"), + PaymentMethod=sub.get("paymentMethodType"), + PaymentMethodId=sub.get("paymentMethodId"), + MemoryStorage=sub.get("memoryStorage"), + CloudProviders=sub.get("cloudDetails"), + RedisVersion=model.RedisVersion, + ) + ) # If no subscriptions are found, return an empty model array LOG.info(f"Final list of models: {models}") @@ -439,4 +503,3 @@ def list_handler( status=OperationStatus.SUCCESS, resourceModels=models, ) - \ No newline at end of file diff --git a/SubscriptionPeering/src/redis_cloudformation_subscriptionpeering/handlers.py b/SubscriptionPeering/src/redis_cloudformation_subscriptionpeering/handlers.py index 1b5199c..3a33296 100644 --- a/SubscriptionPeering/src/redis_cloudformation_subscriptionpeering/handlers.py +++ b/SubscriptionPeering/src/redis_cloudformation_subscriptionpeering/handlers.py @@ -23,78 +23,90 @@ LOG.setLevel("INFO") -#Function using urllib3 that creates and sends the API call -def HttpRequests(method, url, headers, body = None): - response = urllib3.request(method = method, url = url, body = body, headers = headers) + +# Function using urllib3 that creates and sends the API call +def HttpRequests(method, url, headers, body=None): + response = urllib3.request(method=method, url=url, body=body, headers=headers) response = response.json() return response -#Makes the POST API call for Peering -def PostPeering (base_url, event, subscription_id, http_headers): + +# Makes the POST API call for Peering +def PostPeering(base_url, event, subscription_id, http_headers): url = base_url + "/v1/subscriptions/" + str(subscription_id) + "/peerings" - response = HttpRequests(method = "POST", url = url, body = event, headers = http_headers) + response = HttpRequests(method="POST", url=url, body=event, headers=http_headers) LOG.info(f"The POST call response is: {response}") return response -#Returns all the information about Peerings under the specified Subscription -def GetPeering (base_url, subscription_id, http_headers): + +# Returns all the information about Peerings under the specified Subscription +def GetPeering(base_url, subscription_id, http_headers): url = base_url + "/v1/subscriptions/" + str(subscription_id) + "/peerings" - response = HttpRequests(method = "GET", url = url, headers = http_headers) + response = HttpRequests(method="GET", url=url, headers=http_headers) count = 0 - + while "vpcPeeringId" not in str(response) and count < 50: time.sleep(1) count += 1 - response = HttpRequests(method = "GET", url = response['links'][0]['href'], headers = http_headers) + response = HttpRequests( + method="GET", url=response["links"][0]["href"], headers=http_headers + ) - LOG.info(f"Get all Peerings for Subscription with ID {subscription_id} has the response: {response}") + LOG.info( + f"Get all Peerings for Subscription with ID {subscription_id} has the response: {response}" + ) return response -#Function which returns the response of a GET Peering call -def BasicGetPeering (base_url, subscription_id, http_headers): + +# Function which returns the response of a GET Peering call +def BasicGetPeering(base_url, subscription_id, http_headers): url = base_url + "/v1/subscriptions/" + str(subscription_id) + "/peerings" - response = HttpRequests(method = "GET", url = url, headers = http_headers) + response = HttpRequests(method="GET", url=url, headers=http_headers) count = 0 while "resourceId" not in str(response) and count < 50: time.sleep(1) count += 1 - response = HttpRequests(method = "GET", url = response['links'][0]['href'], headers = http_headers) + response = HttpRequests( + method="GET", url=response["links"][0]["href"], headers=http_headers + ) LOG.info(f"The response after basic GET peering is: {response}") return response -#Returns the Peering ID and description used for other API calls -def GetPeeringId (url, http_headers): - response = HttpRequests(method = "GET", url = url, headers = http_headers) + +# Returns the Peering ID and description used for other API calls +def GetPeeringId(url, http_headers): + response = HttpRequests(method="GET", url=url, headers=http_headers) count = 0 - + while "resourceId" not in str(response) and count < 50: time.sleep(1) count += 1 - response = HttpRequests(method = "GET", url = url, headers = http_headers) + response = HttpRequests(method="GET", url=url, headers=http_headers) peer_id = response["response"]["resourceId"] peer_description = response["description"] LOG.info(f"Peering with ID {peer_id} has the response for the GET call: {response}") return peer_id, peer_description -#Function to retrieve peering status to then call the callback_context -def GetPeeringStatus (base_url, subscription_id, vpcId, http_headers): + +# Function to retrieve peering status to then call the callback_context +def GetPeeringStatus(base_url, subscription_id, vpcId, http_headers): peer_url = base_url + "/v1/subscriptions/" + str(subscription_id) + "/peerings" - - response = HttpRequests(method = "GET", url = peer_url, headers = http_headers) + + response = HttpRequests(method="GET", url=peer_url, headers=http_headers) href_value = response["links"][0]["href"] count = 0 while "vpcPeeringId" not in str(response) and count < 50: time.sleep(1) count += 1 - response = HttpRequests(method = "GET", url = href_value, headers = http_headers) + response = HttpRequests(method="GET", url=href_value, headers=http_headers) - LOG.info(f"Peering response after GET call is: {response}") + LOG.info(f"Peering response after GET call is: {response}") peerings = response["response"]["resource"]["peerings"] for peering in peerings: if peering["vpcUid"] == vpcId: @@ -102,50 +114,75 @@ def GetPeeringStatus (base_url, subscription_id, vpcId, http_headers): else: return ProgressEvent.failed( HandlerErrorCode.InternalFailure, - f"Peering status could not be retrieved." + f"Peering status could not be retrieved.", ) -#Makes the PUT API call on Update stack -def PutPeering (base_url, subscription_id, peering_id, event, http_headers): - url = base_url + "/v1/subscriptions/" + str(subscription_id) + "/peerings/" + str(peering_id) - - response = HttpRequests(method = "PUT", url = url, body = event, headers = http_headers) + +# Makes the PUT API call on Update stack +def PutPeering(base_url, subscription_id, peering_id, event, http_headers): + url = ( + base_url + + "/v1/subscriptions/" + + str(subscription_id) + + "/peerings/" + + str(peering_id) + ) + + response = HttpRequests(method="PUT", url=url, body=event, headers=http_headers) LOG.info(f"The PUT call response is: {response}") return response -#Deletes Peering -def DeletePeering (base_url, subscription_id, peering_id, http_headers): - url = base_url + "/v1/subscriptions/" + str(subscription_id) + "/peerings/" + str(peering_id) - - response = HttpRequests(method = "DELETE", url = url, headers = http_headers) + +# Deletes Peering +def DeletePeering(base_url, subscription_id, peering_id, http_headers): + url = ( + base_url + + "/v1/subscriptions/" + + str(subscription_id) + + "/peerings/" + + str(peering_id) + ) + + response = HttpRequests(method="DELETE", url=url, headers=http_headers) LOG.info(f"Response for the FIRST response of deletion is: {response}") count = 0 while count < 50: - if response["status"] == "received" or response["status"] == "processing-in-progress": + if ( + response["status"] == "received" + or response["status"] == "processing-in-progress" + ): time.sleep(1) count += 1 - LOG.info(f"Interogation link for deletion is: {response['links'][0]['href']}") - response = HttpRequests(method = "GET", url = response['links'][0]['href'], headers = http_headers) + LOG.info( + f"Interogation link for deletion is: {response['links'][0]['href']}" + ) + response = HttpRequests( + method="GET", url=response["links"][0]["href"], headers=http_headers + ) LOG.info(f"Response for the link above is: {response}") else: - LOG.info(f"Peering with ID {peering_id} was deleted with response: {response}") + LOG.info( + f"Peering with ID {peering_id} was deleted with response: {response}" + ) return response -#Returns the error message of a wrong peering -def GetPeeringError (url, http_headers): - response = HttpRequests(method = "GET", url = url, headers = http_headers) + +# Returns the error message of a wrong peering +def GetPeeringError(url, http_headers): + response = HttpRequests(method="GET", url=url, headers=http_headers) count = 0 while "processing-error" not in str(response) and count < 50: time.sleep(1) count += 1 - response = HttpRequests(method = "GET", url = url, headers = http_headers) + response = HttpRequests(method="GET", url=url, headers=http_headers) peer_error_description = response["response"]["error"]["description"] LOG.info(f"Peering Creation received the following response: {response}") return peer_error_description + @resource.handler(Action.CREATE) def create_handler( session: Optional[SessionProxy], @@ -159,13 +196,18 @@ def create_handler( resourceModel=model, ) - http_headers = {"accept":"application/json", "x-api-key":typeConfiguration.RedisAccess.xapikey, "x-api-secret-key":typeConfiguration.RedisAccess.xapisecretkey, "Content-Type":"application/json"} + http_headers = { + "accept": "application/json", + "x-api-key": typeConfiguration.RedisAccess.xapikey, + "x-api-secret-key": typeConfiguration.RedisAccess.xapisecretkey, + "Content-Type": "application/json", + } base_url = model.BaseUrl sub_id = model.SubscriptionID provider = model.Provider vpcId = model.VpcId - #Check if we're retrying (if sub_id and sub_status are in callback_context) + # Check if we're retrying (if sub_id and sub_status are in callback_context) if "peer_id" in callback_context and "peer_status" in callback_context: peer_id = callback_context["peer_id"] peer_status = callback_context["peer_status"] @@ -173,12 +215,11 @@ def create_handler( # Check in loop if the Peering is active before existing program if peer_status == "active": LOG.info(f"The Peering status is: {peer_status}.") - return ProgressEvent( - status=OperationStatus.SUCCESS, - resourceModel=model - ) + return ProgressEvent(status=OperationStatus.SUCCESS, resourceModel=model) elif peer_status == "initiating-request" or peer_status == "pending-acceptance": - LOG.info(f"The Peering status is: {peer_status}. Please accept the request from AWS console -> Peering connections.") + LOG.info( + f"The Peering status is: {peer_status}. Please accept the request from AWS console -> Peering connections." + ) peer_status = GetPeeringStatus(base_url, sub_id, vpcId, http_headers) callback_context["peer_id"] = peer_id callback_context["peer_status"] = peer_status @@ -186,56 +227,56 @@ def create_handler( status=OperationStatus.IN_PROGRESS, resourceModel=model, callbackDelaySeconds=60, - callbackContext=callback_context - ) + callbackContext=callback_context, + ) else: return ProgressEvent.failed( HandlerErrorCode.InternalFailure, - f"Peering creation failed with status: {peer_status}" + f"Peering creation failed with status: {peer_status}", ) else: - if provider == "AWS" or provider == '': + if provider == "AWS" or provider == "": event = {} - if model.Region != '': + if model.Region != "": event["region"] = model.Region - if model.AwsAccountId != '': + if model.AwsAccountId != "": event["awsAccountId"] = model.AwsAccountId - if model.VpcId != '': + if model.VpcId != "": event["vpcId"] = model.VpcId - if model.VpcCidr != '': + if model.VpcCidr != "": event["vpcCidr"] = model.VpcCidr - if model.VpcCidrs != '': + if model.VpcCidrs != "": event["vpcCidrs"] = model.VpcCidrs elif provider == "GCP": event = {} - if model.VpcProjectUid != '': + if model.VpcProjectUid != "": event["vpcProjectUid"] = model.VpcProjectUid - if model.VpcNetworkName != '': + if model.VpcNetworkName != "": event["vpcNetworkName"] = model.VpcNetworkName else: return ProgressEvent.failed( HandlerErrorCode.InternalFailure, - f"Incorrect value for 'Provider' field. Please choose one from 'AWS' or 'GCP'." - ) + f"Incorrect value for 'Provider' field. Please choose one from 'AWS' or 'GCP'.", + ) event = json.dumps(event) LOG.info(f"The actual event sent for POST call is: {event}") - #Sending a POST API call to create a Subscription Peering - response = PostPeering (base_url, event, sub_id, http_headers) + # Sending a POST API call to create a Subscription Peering + response = PostPeering(base_url, event, sub_id, http_headers) - #Retrieving the detailed link for Peering after POST call + # Retrieving the detailed link for Peering after POST call href_value = response["links"][0]["href"] - #Retrieving Peering ID and it's Description - peer_id, peer_description = GetPeeringId (href_value, http_headers) + # Retrieving Peering ID and it's Description + peer_id, peer_description = GetPeeringId(href_value, http_headers) peer_id = str(peer_id) model.PeeringID = peer_id LOG.info(f"The Peering ID is: {peer_id}") LOG.info(f"The Peering description is: {peer_description}") - + # Initial status check and storing both in callback_context peer_status = GetPeeringStatus(base_url, sub_id, vpcId, http_headers) callback_context["peer_id"] = peer_id @@ -245,9 +286,10 @@ def create_handler( status=OperationStatus.IN_PROGRESS, resourceModel=model, callbackDelaySeconds=60, - callbackContext=callback_context + callbackContext=callback_context, ) + @resource.handler(Action.UPDATE) def update_handler( session: Optional[SessionProxy], @@ -260,15 +302,20 @@ def update_handler( status=OperationStatus.IN_PROGRESS, resourceModel=model, ) - http_headers = {"accept":"application/json", "x-api-key":typeConfiguration.RedisAccess.xapikey, "x-api-secret-key":typeConfiguration.RedisAccess.xapisecretkey, "Content-Type":"application/json"} + http_headers = { + "accept": "application/json", + "x-api-key": typeConfiguration.RedisAccess.xapikey, + "x-api-secret-key": typeConfiguration.RedisAccess.xapisecretkey, + "Content-Type": "application/json", + } base_url = model.BaseUrl sub_id = model.SubscriptionID peer_id = model.PeeringID event = {} - if model.VpcCidr and model.VpcCidr != '': + if model.VpcCidr and model.VpcCidr != "": event["vpcCidr"] = model.VpcCidr - elif model.VpcCidrs and model.VpcCidrs != '': + elif model.VpcCidrs and model.VpcCidrs != "": event["vpcCidrs"] = model.VpcCidrs else: LOG.info(f"No Updates required.") @@ -294,21 +341,28 @@ def delete_handler( resourceModel=model, ) - http_headers = {"accept":"application/json", "x-api-key":typeConfiguration.RedisAccess.xapikey, "x-api-secret-key":typeConfiguration.RedisAccess.xapisecretkey, "Content-Type":"application/json"} + http_headers = { + "accept": "application/json", + "x-api-key": typeConfiguration.RedisAccess.xapikey, + "x-api-secret-key": typeConfiguration.RedisAccess.xapisecretkey, + "Content-Type": "application/json", + } base_url = model.BaseUrl sub_id = model.SubscriptionID peer_id = model.PeeringID - + try: delete_response = DeletePeering(base_url, sub_id, peer_id, http_headers) # Check if the delete_response indicates a processing error due to not found - if 'response' in str(delete_response) and 'error' in str(delete_response['response']): - error_code = str(delete_response['response']['error']['type']) - if error_code == 'VPC_PEERING_NOT_FOUND': + if "response" in str(delete_response) and "error" in str( + delete_response["response"] + ): + error_code = str(delete_response["response"]["error"]["type"]) + if error_code == "VPC_PEERING_NOT_FOUND": return ProgressEvent.failed( HandlerErrorCode.NotFound, - f"Peering with ID {peer_id} under Subscription {sub_id} has the description: {delete_response['response']['error']['description']}" + f"Peering with ID {peer_id} under Subscription {sub_id} has the description: {delete_response['response']['error']['description']}", ) # If the delete call was successful but resource still exists, handle that case @@ -317,16 +371,14 @@ def delete_handler( if str(peer_id) in str(response_check): return ProgressEvent.failed( HandlerErrorCode.InternalFailure, - f"Peering with ID {peer_id} under Subscription {sub_id} still exists." + f"Peering with ID {peer_id} under Subscription {sub_id} still exists.", ) return ProgressEvent(status=OperationStatus.SUCCESS) except Exception as e: - return ProgressEvent.failed( - HandlerErrorCode.InternalFailure, - str(e) - ) + return ProgressEvent.failed(HandlerErrorCode.InternalFailure, str(e)) + @resource.handler(Action.READ) def read_handler( @@ -336,8 +388,13 @@ def read_handler( ) -> ProgressEvent: model = request.desiredResourceState typeConfiguration = request.typeConfiguration - - http_headers = {"accept":"application/json", "x-api-key":typeConfiguration.RedisAccess.xapikey, "x-api-secret-key":typeConfiguration.RedisAccess.xapisecretkey, "Content-Type":"application/json"} + + http_headers = { + "accept": "application/json", + "x-api-key": typeConfiguration.RedisAccess.xapikey, + "x-api-secret-key": typeConfiguration.RedisAccess.xapisecretkey, + "Content-Type": "application/json", + } base_url = model.BaseUrl sub_id = model.SubscriptionID peer_id = model.PeeringID @@ -350,7 +407,7 @@ def read_handler( LOG.info(f"Peering with ID {peer_id} not found. Returning NotFound error.") return ProgressEvent.failed( HandlerErrorCode.NotFound, - f"Peering with ID {peer_id} under Subscription {sub_id} does not exist." + f"Peering with ID {peer_id} under Subscription {sub_id} does not exist.", ) else: # If the resource still exists, return it @@ -361,6 +418,7 @@ def read_handler( resourceModel=model, ) + @resource.handler(Action.LIST) def list_handler( session: Optional[SessionProxy], @@ -370,7 +428,12 @@ def list_handler( model = request.desiredResourceState typeConfiguration = request.typeConfiguration - http_headers = {"accept":"application/json", "x-api-key":typeConfiguration.RedisAccess.xapikey, "x-api-secret-key":typeConfiguration.RedisAccess.xapisecretkey, "Content-Type":"application/json"} + http_headers = { + "accept": "application/json", + "x-api-key": typeConfiguration.RedisAccess.xapikey, + "x-api-secret-key": typeConfiguration.RedisAccess.xapisecretkey, + "Content-Type": "application/json", + } base_url = model.BaseUrl sub_id = model.SubscriptionID @@ -383,8 +446,16 @@ def list_handler( peerings = response["response"]["resource"]["peerings"] LOG.info(f"These are the peerings: {peerings}") for peering in peerings: - models.append(ResourceModel( - Provider="AWS" if (peering.get("awsAccountId")!= None or peering.get("awsAccountId")!= '') else "GCP", + models.append( + ResourceModel( + Provider=( + "AWS" + if ( + peering.get("awsAccountId") != None + or peering.get("awsAccountId") != "" + ) + else "GCP" + ), PeeringID=str(peering.get("vpcPeeringId")), AwsAccountId=peering.get("awsAccountId"), Region=peering.get("regionName"), @@ -395,29 +466,31 @@ def list_handler( VpcNetworkName=peering.get("vpcNetworkName"), SubscriptionID=response["response"]["resourceId"], BaseUrl=base_url, - )) + ) + ) LOG.info(f"This is the list of models: {models}") return ProgressEvent( status=OperationStatus.SUCCESS, resourceModels=models, ) else: - models.append(ResourceModel( - Provider="", - PeeringID="", - AwsAccountId="", - Region="", - VpcId="", - VpcCidrs="", - VpcCidr="", - VpcProjectUid="", - VpcNetworkName="", - SubscriptionID="", - BaseUrl="", - )) + models.append( + ResourceModel( + Provider="", + PeeringID="", + AwsAccountId="", + Region="", + VpcId="", + VpcCidrs="", + VpcCidr="", + VpcProjectUid="", + VpcNetworkName="", + SubscriptionID="", + BaseUrl="", + ) + ) LOG.info(f"This is the list of models: {models}") return ProgressEvent( status=OperationStatus.SUCCESS, resourceModels=models, ) -