Maintain Cloud Accounts Information
How can we maintain the relevant cloud accounts information for a particular user?
These code snippets demonstrate how the chalice server maintains the cloud accounts information for a particular user. Note that this code will NOT be executed unless a valid Rosetta JWT has been presented at the API endpoint along with this request. Also note that cloud accounts are synonymous with target sites...the idea is that users are targeting the sites where their cloud accounts exist.
Once the Rosetta JWT has been validated, the chalice server simply makes the appropriate changes in DynamoDB. Deleting a cloud account is pretty straightforward. Adding a cloud account is also pretty straightforward; however, take note that I did need to "curate" the dictionary/map of items before storing them in DynamoDB (as it did not allow null or empty strings in some cases). Finally, updating a cloud account is also reasonably straightforward; but, in addition to needing to "curate" the information, the old information and the new information need to be "merged" together and then persisted.
--
app.py
--
from chalice import Chalice
from chalicelib import requestUtility
app = Chalice(app_name='rosetta-api-chalice')
#
# API endpoints - target-sites
#
@app.route('/target-sites', methods=['POST'])
def createTargetSite():
userId = validateUser(app.current_request.headers)
inputSiteInfo = app.current_request.json_body
newSiteMemento = requestUtility.RequestUtility().createTargetSite(userId, inputSiteInfo)
return newSiteMemento
@app.route('/target-sites/{siteId}', methods=['DELETE'])
def deleteTargetSite(siteId):
userId = validateUser(app.current_request.headers)
return requestUtility.RequestUtility().deleteTargetSite(userId, siteId)
@app.route('/target-sites/{siteId}', methods=['PATCH'])
def updateTargetSite(siteId):
userId = validateUser(app.current_request.headers)
inputSiteInfo = app.current_request.json_body
return requestUtility.RequestUtility().updateTargetSite(userId, siteId, inputSiteInfo)
--
chalicelib/requestUtility.py
--
from . dynamoDbUtility import DynamoDbUtility
class RequestUtility(object):
def createTargetSite(self, userId, inputSiteInfo):
return DynamoDbUtility().createTargetSite(userId, inputSiteInfo)
def deleteTargetSite(self, userId, siteId):
return DynamoDbUtility().deleteTargetSite(userId, siteId)
def updateTargetSite(self, userId, siteId, inputSiteInfo):
return DynamoDbUtility().updateTargetSite(userId, siteId, inputSiteInfo)
--
chalicelib/dynamoDbUtility.py
--
import binascii
import boto3
import os
import time
import uuid
from boto3.dynamodb.conditions import Key
ddbClient = boto3.resource('dynamodb')
ddbPendingTokenTable = ddbClient.Table('PendingToken')
ddbTargetSiteTable = ddbClient.Table('TargetSite')
class DynamoDbUtility(object):
# region - Public Methods - Target Site
def createTargetSite(self, userId, inputSiteInfo):
siteId = str(uuid.uuid4())
siteInfo = self._createSiteInfo(userId, inputSiteInfo)
ddbResponse = ddbTargetSiteTable.put_item(
Item={
'userId': userId,
'siteId': siteId,
'siteInfo': siteInfo
}
)
if ddbResponse is None:
return None
elif ddbResponse.get('ResponseMetadata', {}).get('HTTPStatusCode') != 200:
return None
siteName = siteInfo.get('siteName')
return {
'siteName': siteName,
'siteId': siteId
}
def deleteTargetSite(self, userId, siteId):
ddbResponse = ddbTargetSiteTable.delete_item(
Key={
'userId': userId,
'siteId': siteId
}
)
if ddbResponse is None:
return False
elif ddbResponse.get('ResponseMetadata', {}).get('HTTPStatusCode') != 200:
return False
else:
return True
def updateTargetSite(self, userId, siteId, inputSiteInfo):
# guard clause - problem
existingSiteInfo = self.getTargetSite(userId, siteId)
if existingSiteInfo is None:
return None
mergedSiteInfo = self._mergeSiteInfo(userId, existingSiteInfo, inputSiteInfo)
ddbResponse = ddbTargetSiteTable.update_item(
Key={
'userId': userId,
'siteId': siteId
},
AttributeUpdates={
'siteInfo': {
'Value': mergedSiteInfo,
'Action': 'PUT'
}
}
)
if ddbResponse is None:
return None
elif ddbResponse.get('ResponseMetadata', {}).get('HTTPStatusCode') != 200:
return None
else:
return mergedSiteInfo
# region - Helper Methods
def _createSiteInfo(self, userId, inputSiteInfo):
# Put together siteInfo dictionary based on inputSiteInfo
return self._getDdbCuratedMap(siteInfo)
def _getDdbCuratedMap(self, oldMap):
# guard clause - no value
if oldMap is None:
return {}
# DDB complains about empty string/None values in maps
newMap = {}
for oldKey in oldMap.keys():
oldValue = oldMap.get(oldKey)
if oldValue is not None and oldValue != '':
newMap[oldKey] = oldValue
return newMap
def _getNumberValue(self, mapKey, existingMap, inputMap, defaultValue):
# guard clause - precedence to input map
inputValue = inputMap.get(mapKey)
if inputValue is not None:
return inputValue
# guard clause - then to existing map
existingValue = existingMap.get(mapKey)
if existingValue is not None:
return existingValue
return defaultValue
def _getStringValue(self, mapKey, existingMap, inputMap, defaultValue):
# guard clause - precedence to input map
inputValue = inputMap.get(mapKey)
if inputValue is not None and inputValue != '':
return inputValue
# guard clause - then to existing map
existingValue = existingMap.get(mapKey)
if existingValue is not None and existingValue != '':
return existingValue
return defaultValue
def _mergeSiteInfo(self, userId, existingSiteInfo, inputSiteInfo):
# Put together mergedSiteInfo dictionary from existingSiteInfo and inputSiteInfo
return self._getDdbCuratedMap(mergedSiteInfo)
--