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)

--