Skip to content

Commit

Permalink
feat: add tenant-registration service (#109)
Browse files Browse the repository at this point in the history
* feat: add tenant-registration service

* clean up and fix permission error for calling tenant mgmt from tenant reg service

* remove commented bash code

* chore: self mutation

Signed-off-by: github-actions <[email protected]>

* clean up commented code and add back user api testing

* add tests for reading tenant info from tenant service

* fix incorrect refs to tenant mgmt and update api.md docs

---------

Signed-off-by: github-actions <[email protected]>
Co-authored-by: github-actions <[email protected]>
  • Loading branch information
suhussai and github-actions authored Nov 6, 2024
1 parent 2716e5e commit 0109bb9
Show file tree
Hide file tree
Showing 21 changed files with 1,594 additions and 472 deletions.
1 change: 0 additions & 1 deletion .projenrc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,6 @@ const jsiiLibraryProjectOptions: cdk.JsiiProjectOptions = {
sampleCode: false,
stability: 'experimental',
workflowNodeVersion: '20.x',

npmTokenSecret: 'NPM_TOKEN',
npmAccess: NpmAccess.PUBLIC,
githubOptions: GITHUB_OPTIONS,
Expand Down
712 changes: 662 additions & 50 deletions API.md

Large diffs are not rendered by default.

158 changes: 35 additions & 123 deletions resources/functions/tenant-management/index.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: Apache-2.0

import json
import os
from http import HTTPStatus
import uuid
Expand All @@ -11,8 +10,7 @@
import botocore
from typing import Optional
from aws_lambda_powertools import Logger, Tracer
from aws_lambda_powertools.event_handler import (APIGatewayHttpResolver,
CORSConfig)
from aws_lambda_powertools.event_handler import APIGatewayHttpResolver, CORSConfig
from aws_lambda_powertools.logging import correlation_paths
from aws_lambda_powertools.event_handler.openapi.params import Query, Path
from aws_lambda_powertools.shared.types import Annotated
Expand All @@ -27,59 +25,43 @@
cors_config = CORSConfig(allow_origin="*", max_age=300)
app = APIGatewayHttpResolver(cors=cors_config, enable_validation=True)

event_bus = boto3.client('events')
eventbus_name = os.environ['EVENTBUS_NAME']
event_source = os.environ['EVENT_SOURCE']
onboarding_detail_type = os.environ['ONBOARDING_DETAIL_TYPE']
offboarding_detail_type = os.environ['OFFBOARDING_DETAIL_TYPE']
activate_detail_type = os.environ['ACTIVATE_DETAIL_TYPE']
deactivate_detail_type = os.environ['DEACTIVATE_DETAIL_TYPE']
dynamodb = boto3.resource('dynamodb')
tenant_details_table = dynamodb.Table(os.environ['TENANT_DETAILS_TABLE'])
dynamodb = boto3.resource("dynamodb")
tenant_details_table = dynamodb.Table(os.environ["TENANT_DETAILS_TABLE"])


@app.post("/tenants")
@tracer.capture_method
def create_tenant():
input_details = app.current_event.json_body
input_item = {}
input_details['tenantId'] = str(uuid.uuid4())
input_details["tenantId"] = str(uuid.uuid4())
input_details["sbtaws_active"] = True

logger.info("Request received to create new tenant")

try:
for key, value in input_details.items():
input_item[key] = value

input_item['isActive'] = True

response = tenant_details_table.put_item(Item=input_item)
response = tenant_details_table.put_item(Item=input_details)
logger.info(f"put_item response {response}")
__create_control_plane_event(
json.dumps(input_details), onboarding_detail_type)

except botocore.exceptions.ClientError as error:
logger.error(error)
raise InternalServerError("Unknown error during processing!")
else:
return {'data': input_item}, HTTPStatus.CREATED
return {"data": input_details}, HTTPStatus.CREATED


@app.get("/tenants")
@tracer.capture_method
def get_tenants(limit: Annotated[Optional[int], Query(gt=0)] = 10,
next_token: Annotated[Optional[str], Query(min_length=0)] = None):
def get_tenants(
limit: Annotated[Optional[int], Query(gt=0)] = 10,
next_token: Annotated[Optional[str], Query(min_length=0)] = None,
):
logger.info("Request received to get all tenants")
tenants = None
last_evaluated_key = None

kwargs = {
'Limit': limit
}
kwargs = {"Limit": limit}
if next_token:
kwargs['ExclusiveStartKey'] = {
'tenantId': next_token
}
kwargs["ExclusiveStartKey"] = {"tenantId": next_token}

try:
response = tenant_details_table.scan(**kwargs)
Expand All @@ -91,12 +73,10 @@ def get_tenants(limit: Annotated[Optional[int], Query(gt=0)] = 10,
raise InternalServerError("Unknown error during processing!")

else:
return_response = {
"data": tenants
}
return_response = {"data": tenants}

if last_evaluated_key:
return_response["next_token"] = last_evaluated_key['tenantId']
return_response["next_token"] = last_evaluated_key["tenantId"]

return return_response, HTTPStatus.OK

Expand All @@ -107,15 +87,15 @@ def get_tenant(tenantId: Annotated[str, Path(min_length=0)]):
logger.info(f"Request received to get a tenant: {tenantId}")
tenant = None
try:
response = tenant_details_table.get_item(Key={'tenantId': tenantId})
tenant = response.get('Item')
response = tenant_details_table.get_item(Key={"tenantId": tenantId})
tenant = response.get("Item")
if not tenant:
raise NotFoundError(f"Tenant not found for id {tenantId}")
except botocore.exceptions.ClientError as error:
logger.error(error)
raise InternalServerError("Unknown error during processing!")
else:
return {'data': tenant}, HTTPStatus.OK
return {"data": tenant}, HTTPStatus.OK


@app.put("/tenants/<tenantId>")
Expand All @@ -126,45 +106,45 @@ def update_tenant(tenantId: Annotated[str, Path(min_length=0)]):
updated_tenant = None
try:
response = __update_tenant(tenantId, input_details)
updated_tenant = response['Attributes']
updated_tenant = response["Attributes"]
except dynamodb.meta.client.exceptions.ConditionalCheckFailedException:
logger.info(f'received request to update non-existing tenant {tenantId}')
logger.info(f"received request to update non-existing tenant {tenantId}")
raise NotFoundError(f"Tenant {tenantId} not found.")
except botocore.exceptions.ClientError as error:
logger.error(error)
raise InternalServerError("Unknown error during processing!")
else:
return {'data': updated_tenant}, HTTPStatus.OK
return {"data": updated_tenant}, HTTPStatus.OK


@app.delete("/tenants/<tenantId>")
@tracer.capture_method
def delete_tenant(tenantId: Annotated[str, Path(min_length=0)]):
logger.info("Request received to delete a tenant")
deleted_tenant = None

try:
response = __update_tenant(tenantId, {'tenantStatus': 'Deleting'})
__create_control_plane_event(
json.dumps(response['Attributes']), offboarding_detail_type)
response = __update_tenant(tenantId, {"sbtaws_active": False})
deleted_tenant = response["Attributes"]
except dynamodb.meta.client.exceptions.ConditionalCheckFailedException:
logger.info(f'received request to update non-existing tenant {tenantId}')
logger.info(f"received request to update non-existing tenant {tenantId}")
raise NotFoundError(f"Tenant {tenantId} not found.")
except botocore.exceptions.ClientError as error:
logger.error(error)
raise InternalServerError("Unknown error during processing!")
else:
return {"message": "Successfully sent offboarding message to application plane"}, HTTPStatus.OK
return {"data": deleted_tenant}, HTTPStatus.OK


def __update_tenant(tenantId, tenant):
# Remove the tenantId if the incoming object has one
input_details = {key: tenant[key] for key in tenant if key != 'tenantId'}
input_details = {key: tenant[key] for key in tenant if key != "tenantId"}
update_expression = []
update_expression.append("set ")
expression_attribute_values = {}
for key, value in input_details.items():
key_variable = f":{key}Variable"
update_expression.append(''.join([key, " = ", key_variable]))
update_expression.append("".join([key, " = ", key_variable]))
update_expression.append(",")
expression_attribute_values[key_variable] = value

Expand All @@ -173,86 +153,18 @@ def __update_tenant(tenantId, tenant):

return tenant_details_table.update_item(
Key={
'tenantId': tenantId,
"tenantId": tenantId,
},
ConditionExpression=Attr('tenantId').eq(tenantId),
UpdateExpression=''.join(update_expression),
ConditionExpression=Attr("tenantId").eq(tenantId),
UpdateExpression="".join(update_expression),
ExpressionAttributeValues=expression_attribute_values,
ReturnValues="ALL_NEW"
)


@app.put("/tenants/<tenantId>/deactivate")
@tracer.capture_method
def deactivate_tenant(tenantId: Annotated[str, Path(min_length=0)]):
logger.info("Request received to deactivate a tenant")

try:
response = tenant_details_table.update_item(
Key={
'tenantId': tenantId,
},
UpdateExpression="set isActive = :isActive",
ExpressionAttributeValues={
':isActive': False
},
ReturnValues="ALL_NEW"
)
logger.info(f"update_item response {response}")

__create_control_plane_event(
json.dumps({"tenantId": tenantId}), deactivate_detail_type)
except botocore.exceptions.ClientError as error:
logger.error(error)
raise InternalServerError("Unknown error during processing!")

else:
return {"message": "Tenant deactivated"}, HTTPStatus.OK


@app.put("/tenants/<tenantId>/activate")
@tracer.capture_method
def activate_tenant(tenantId: Annotated[str, Path(min_length=0)]):
logger.info("Request received to activate a tenant")

try:
response = tenant_details_table.update_item(
Key={
'tenantId': tenantId,
},
UpdateExpression="set isActive = :isActive",
ExpressionAttributeValues={
':isActive': True
},
ReturnValues="ALL_NEW"
)
logger.info(f"update_item response {response}")

__create_control_plane_event(json.dumps(
response['Attributes']), activate_detail_type)
except botocore.exceptions.ClientError as error:
logger.error(error)
raise InternalServerError("Unknown error during processing!")

else:
return {"message": "Tenant activated"}, HTTPStatus.OK


def __create_control_plane_event(event_details, detail_type):
response = event_bus.put_events(
Entries=[
{
'EventBusName': eventbus_name,
'Source': event_source,
'DetailType': detail_type,
'Detail': event_details,
}
]
ReturnValues="ALL_NEW",
)
logger.info(response)


@logger.inject_lambda_context(correlation_id_path=correlation_paths.API_GATEWAY_HTTP, log_event=True)
@logger.inject_lambda_context(
correlation_id_path=correlation_paths.API_GATEWAY_HTTP, log_event=True
)
@tracer.capture_lambda_handler
def lambda_handler(event, context):
logger.debug(event)
Expand Down
Loading

0 comments on commit 0109bb9

Please sign in to comment.