├── __init__.py ├── test ├── __init__.py ├── data │ ├── album_sales.cli │ ├── album_sales.json │ ├── sample.yaml.template │ └── sample.template ├── test_cloud_formation_parser.py └── test_dynamodb_resource_parser.py ├── .gitignore ├── dynamodb_cloud_formation ├── __init__.py ├── cloud_formation_parser.py └── dynamodb_resource_parser.py ├── LICENSE ├── parse.py └── README.md /__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | __pycache__ 3 | -------------------------------------------------------------------------------- /dynamodb_cloud_formation/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/data/album_sales.cli: -------------------------------------------------------------------------------- 1 | aws dynamodb create-table --region us-east-1 --endpoint-url http://localhost:8000 --table-name AlbumSales --attribute-definitions '[{"AttributeName": "Album", "AttributeType": "S"}, {"AttributeName": "Artist", "AttributeType": "S"}, {"AttributeName": "Sales", "AttributeType": "N"}]' --key-schema '[{"AttributeName": "Album", "KeyType": "HASH"}, {"AttributeName": "Artist", "KeyType": "RANGE"}]' --local-secondary-indexes '[{"IndexName": "myLSI", "KeySchema": [{"AttributeName": "Album", "KeyType": "HASH"}, {"AttributeName": "Sales", "KeyType": "RANGE"}], "Projection": {"NonKeyAttributes": ["Artist"], "ProjectionType": "INCLUDE"}}]' --global-secondary-indexes '[{"IndexName": "myGSI", "KeySchema": [{"AttributeName": "Sales", "KeyType": "HASH"}, {"AttributeName": "Artist", "KeyType": "RANGE"}], "Projection": {"NonKeyAttributes": ["Album"], "ProjectionType": "INCLUDE"}, "ProvisionedThroughput": {"ReadCapacityUnits": 5, "WriteCapacityUnits": 5}}]' --provisioned-throughput '{"ReadCapacityUnits": 5, "WriteCapacityUnits": 5}' 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Steven Bruce 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /test/test_cloud_formation_parser.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from dynamodb_cloud_formation.cloud_formation_parser import CloudFormationParser 3 | 4 | 5 | class TestCloudFormationParser(unittest.TestCase): 6 | def parse(self, templateFileName, expectedTableNames): 7 | # parse the Cloud Formation Resource 8 | cloudFormationParser = CloudFormationParser() 9 | tables = cloudFormationParser.parse_cloud_formation_template(templateFileName) 10 | 11 | tableNames = list(map((lambda table: table.json['Properties']['TableName']), tables)) 12 | self.assertEqual(tableNames, expectedTableNames) 13 | 14 | # verify parsing of the dynamodb resource 15 | 16 | def test_sample_template(self): 17 | # ensure that myTableName is constructed before myTableName2 18 | self.parse('test/data/sample.template', ['myTableName', 'myTableName2']) 19 | 20 | def test_sample_yaml_template(self): 21 | # ensure that myTableName is constructed before myTableName2 22 | self.parse('test/data/sample.yaml.template', ['myTableName', 'myTableName2']) 23 | 24 | 25 | suite = unittest.TestLoader().loadTestsFromTestCase(TestCloudFormationParser) 26 | unittest.TextTestRunner(verbosity=2).run(suite) 27 | -------------------------------------------------------------------------------- /parse.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import json 5 | import sys 6 | from dynamodb_cloud_formation.cloud_formation_parser import CloudFormationParser 7 | 8 | import argparse 9 | 10 | parser = argparse.ArgumentParser(description='Translate AWS Cloud Formation template to AWS CLI commands') 11 | parser.add_argument('filename', metavar='', 12 | help='The cloud formation template') 13 | parser.add_argument('--region', dest='region', metavar='', default='us-east-1', 14 | help='The AWS region to simulate when creating DynamoDB local tables') 15 | 16 | parser.add_argument('--endpoint-url', dest='endpoint', metavar='', default='http://localhost:8000', 17 | help='The DynamoDB local endpoint url') 18 | 19 | args = parser.parse_args() 20 | 21 | try: 22 | cloudFormationParser = CloudFormationParser() 23 | 24 | # iterate through tables in dependency order 25 | for table in cloudFormationParser.parse_cloud_formation_template(args.filename): 26 | print table.toCLI(args.region, args.endpoint) 27 | 28 | except IOError: 29 | print "Unable to open cloud formation template: " + args.filename 30 | sys.exit(-2) 31 | except ValueError: 32 | print "Unable to parse open cloud formation template: " + args.filename 33 | sys.exit(-3) 34 | 35 | sys.exit(0) 36 | -------------------------------------------------------------------------------- /test/data/album_sales.json: -------------------------------------------------------------------------------- 1 | { 2 | "Type" : "AWS::DynamoDB::Table", 3 | "Properties" : { 4 | "AttributeDefinitions" : [ 5 | { 6 | "AttributeName" : "Album", 7 | "AttributeType" : "S" 8 | }, 9 | { 10 | "AttributeName" : "Artist", 11 | "AttributeType" : "S" 12 | }, 13 | { 14 | "AttributeName" : "Sales", 15 | "AttributeType" : "N" 16 | } 17 | ], 18 | "KeySchema" : [ 19 | { 20 | "AttributeName" : "Album", 21 | "KeyType" : "HASH" 22 | }, 23 | { 24 | "AttributeName" : "Artist", 25 | "KeyType" : "RANGE" 26 | } 27 | ], 28 | "ProvisionedThroughput" : { 29 | "ReadCapacityUnits" : "5", 30 | "WriteCapacityUnits" : "5" 31 | }, 32 | "TableName" : "AlbumSales", 33 | "GlobalSecondaryIndexes" : [{ 34 | "IndexName" : "myGSI", 35 | "KeySchema" : [ 36 | { 37 | "AttributeName" : "Sales", 38 | "KeyType" : "HASH" 39 | }, 40 | { 41 | "AttributeName" : "Artist", 42 | "KeyType" : "RANGE" 43 | } 44 | ], 45 | "Projection" : { 46 | "NonKeyAttributes" : ["Album"], 47 | "ProjectionType" : "INCLUDE" 48 | }, 49 | "ProvisionedThroughput" : { 50 | "ReadCapacityUnits" : "5", 51 | "WriteCapacityUnits" : "5" 52 | } 53 | }], 54 | "LocalSecondaryIndexes" :[{ 55 | "IndexName" : "myLSI", 56 | "KeySchema" : [ 57 | { 58 | "AttributeName" : "Album", 59 | "KeyType" : "HASH" 60 | }, 61 | { 62 | "AttributeName" : "Sales", 63 | "KeyType" : "RANGE" 64 | } 65 | ], 66 | "Projection" : { 67 | "NonKeyAttributes" : ["Artist"], 68 | "ProjectionType" : "INCLUDE" 69 | } 70 | }] 71 | } 72 | } -------------------------------------------------------------------------------- /test/test_dynamodb_resource_parser.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import json 3 | from dynamodb_cloud_formation.dynamodb_resource_parser import DynamoDbResourceParser 4 | 5 | 6 | class TestDynamoDbResourceParser(unittest.TestCase): 7 | 8 | def loadTextFile(self, filename): 9 | # load the contents of a text file 10 | file = open(filename, 'r') 11 | text = file.read() 12 | file.close() 13 | return text 14 | 15 | def loadJson(self, filename): 16 | # load the resource as JSON 17 | with open(filename) as fileHandle: 18 | jsonData = json.load(fileHandle) 19 | return jsonData 20 | 21 | def parse(self, resourceFileName, expectedCliFileName): 22 | 23 | # load test data 24 | expected = self.loadTextFile(expectedCliFileName) 25 | resourceJson = self.loadJson(resourceFileName) 26 | 27 | # parse the Cloud Formation Resource 28 | dynamoDbResource = DynamoDbResourceParser(resourceJson) 29 | command = dynamoDbResource.toCLI('us-east-1','http://localhost:8000') 30 | 31 | # verify parsing of the dynamodb resource 32 | self.assertEqual(command, expected, ''.join(('Error parsing DynamoDb configuration for: ', 33 | resourceFileName, 34 | '\nexpected: ', 35 | expected, 36 | '\nactual : ', 37 | command, 38 | ''))) 39 | 40 | def test_album_sales(self): 41 | # execute the test for the album_sales table 42 | self.parse('test/data/album_sales.json', 'test/data/album_sales.cli') 43 | 44 | 45 | 46 | 47 | suite = unittest.TestLoader().loadTestsFromTestCase(TestDynamoDbResourceParser) 48 | unittest.TextTestRunner(verbosity=2).run(suite) -------------------------------------------------------------------------------- /dynamodb_cloud_formation/cloud_formation_parser.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import json 5 | import yaml 6 | from dynamodb_cloud_formation.dynamodb_resource_parser import DynamoDbResourceParser 7 | 8 | 9 | class CloudFormationParser: 10 | def __init__(self): 11 | self.dynamo_db_resources = {} 12 | self.dynamo_db_resource_list = [] 13 | self.processed = set() 14 | 15 | def load_json(self, filename): 16 | # load the resource as JSON 17 | with open(filename) as fileHandle: 18 | jsonData = json.load(fileHandle) 19 | return jsonData 20 | 21 | def load_yaml(self, filename): 22 | # load the resource as YAML 23 | with open(filename) as fileHandle: 24 | yamlData = yaml.load(fileHandle) 25 | return yamlData 26 | 27 | def parse_cloud_formation_template(self, file_name): 28 | try: 29 | cloud_formation_json = self.load_yaml(file_name) 30 | except yaml.scanner.ScannerError: 31 | cloud_formation_json = self.load_json(file_name) 32 | 33 | resources = cloud_formation_json['Resources'] 34 | 35 | # parse all the DynamoDb resources in the cloud formation template 36 | for resource_id in resources.keys(): 37 | resource = resources[resource_id] 38 | if resource['Type'] == 'AWS::DynamoDB::Table': 39 | dynamoDbResource = DynamoDbResourceParser(resource) 40 | self.dynamo_db_resources[resource_id] = dynamoDbResource 41 | 42 | # build a list of tables in creation order 43 | for resourceId in self.dynamo_db_resources.keys(): 44 | self.outputTable(resourceId) 45 | 46 | # return the list of tables 47 | return self.dynamo_db_resource_list 48 | 49 | def outputTable(self, resourceId): 50 | if resourceId not in self.processed: 51 | table = self.dynamo_db_resources[resourceId] 52 | 53 | # pre-emptively mark the table as processed to avoid endless recursion 54 | self.processed.add(resourceId) 55 | 56 | if table.dependency() != "": 57 | # recursively add the dependency first 58 | self.outputTable(table.dependency()) 59 | 60 | # add the table to our list 61 | self.dynamo_db_resource_list.append(table) 62 | -------------------------------------------------------------------------------- /test/data/sample.yaml.template: -------------------------------------------------------------------------------- 1 | AWSTemplateFormatVersion: '2010-09-09' 2 | Resources: 3 | myDynamoDBTable: 4 | Type: AWS::DynamoDB::Table 5 | Properties: 6 | AttributeDefinitions: 7 | - AttributeName: Album 8 | AttributeType: S 9 | - AttributeName: Artist 10 | AttributeType: S 11 | - AttributeName: Sales 12 | AttributeType: N 13 | KeySchema: 14 | - AttributeName: Album 15 | KeyType: HASH 16 | - AttributeName: Artist 17 | KeyType: RANGE 18 | ProvisionedThroughput: 19 | ReadCapacityUnits: '5' 20 | WriteCapacityUnits: '5' 21 | TableName: myTableName 22 | GlobalSecondaryIndexes: 23 | - IndexName: myGSI 24 | KeySchema: 25 | - AttributeName: Sales 26 | KeyType: HASH 27 | - AttributeName: Artist 28 | KeyType: RANGE 29 | Projection: 30 | NonKeyAttributes: 31 | - Album 32 | ProjectionType: INCLUDE 33 | ProvisionedThroughput: 34 | ReadCapacityUnits: '5' 35 | WriteCapacityUnits: '5' 36 | LocalSecondaryIndexes: 37 | - IndexName: myLSI 38 | KeySchema: 39 | - AttributeName: Album 40 | KeyType: HASH 41 | - AttributeName: Sales 42 | KeyType: RANGE 43 | Projection: 44 | NonKeyAttributes: 45 | - Artist 46 | ProjectionType: INCLUDE 47 | mySecondDDBTable: 48 | Type: AWS::DynamoDB::Table 49 | DependsOn: myDynamoDBTable 50 | Properties: 51 | AttributeDefinitions: 52 | - AttributeName: ArtistId 53 | AttributeType: S 54 | - AttributeName: Concert 55 | AttributeType: S 56 | - AttributeName: TicketSales 57 | AttributeType: S 58 | KeySchema: 59 | - AttributeName: ArtistId 60 | KeyType: HASH 61 | - AttributeName: Concert 62 | KeyType: RANGE 63 | TableName: myTableName2 64 | ProvisionedThroughput: 65 | ReadCapacityUnits: '5' 66 | WriteCapacityUnits: '5' 67 | GlobalSecondaryIndexes: 68 | - IndexName: myGSI 69 | KeySchema: 70 | - AttributeName: TicketSales 71 | KeyType: HASH 72 | Projection: 73 | ProjectionType: KEYS_ONLY 74 | ProvisionedThroughput: 75 | ReadCapacityUnits: '5' 76 | WriteCapacityUnits: '5' -------------------------------------------------------------------------------- /dynamodb_cloud_formation/dynamodb_resource_parser.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | import json 5 | 6 | class DynamoDbResourceParser: 7 | def __init__(self, resourceJson): 8 | self.json = resourceJson 9 | 10 | def dependency(self): 11 | if 'DependsOn' in self.json: 12 | return self.json['DependsOn'] 13 | else: 14 | return "" 15 | 16 | def tableName(self): 17 | return " --table-name " + self.json['Properties']['TableName'] 18 | 19 | def attributeDefinitions(self): 20 | 21 | attributeDefinitions = self.json['Properties']['AttributeDefinitions'] 22 | if len(attributeDefinitions) == 0: 23 | return "" 24 | return " --attribute-definitions '" + json.JSONEncoder(sort_keys=True).encode(attributeDefinitions) + "'" 25 | 26 | def keySchema(self): 27 | 28 | keySchema = self.json['Properties']['KeySchema'] 29 | if len(keySchema) == 0: 30 | return "" 31 | 32 | return " --key-schema '" + json.JSONEncoder(sort_keys=True).encode(keySchema) + "'" 33 | 34 | def provisionedThroughput(self): 35 | if 'ProvisionedThroughput' in self.json['Properties']: 36 | provisionedThroughput = self.json['Properties']['ProvisionedThroughput'] 37 | 38 | provisionedThroughput['ReadCapacityUnits'] = int(provisionedThroughput['ReadCapacityUnits']) 39 | provisionedThroughput['WriteCapacityUnits'] = int(provisionedThroughput['WriteCapacityUnits']) 40 | return " --provisioned-throughput '" + json.JSONEncoder(sort_keys=True).encode(provisionedThroughput) + "'" 41 | else: 42 | return "" 43 | 44 | def localSecondaryIndexes(self): 45 | if 'LocalSecondaryIndexes' in self.json['Properties']: 46 | localSecondaryIndexes = self.json['Properties']['LocalSecondaryIndexes'] 47 | if len(localSecondaryIndexes) == 0: 48 | return "" 49 | 50 | for index in localSecondaryIndexes: 51 | if "ProvisionedThroughput" in index: 52 | provisionedThroughput = index['ProvisionedThroughput'] 53 | provisionedThroughput['ReadCapacityUnits'] = int(provisionedThroughput['ReadCapacityUnits']) 54 | provisionedThroughput['WriteCapacityUnits'] = int(provisionedThroughput['WriteCapacityUnits']) 55 | 56 | return " --local-secondary-indexes '" + json.JSONEncoder(sort_keys=True).encode(localSecondaryIndexes) + "'" 57 | else: 58 | return "" 59 | 60 | 61 | def globalSecondaryIndexes(self): 62 | if 'GlobalSecondaryIndexes' in self.json['Properties']: 63 | globalSecondaryIndexes = self.json['Properties']['GlobalSecondaryIndexes'] 64 | if len(globalSecondaryIndexes) == 0: 65 | return "" 66 | 67 | for index in globalSecondaryIndexes: 68 | if "ProvisionedThroughput" in index: 69 | provisionedThroughput = index['ProvisionedThroughput'] 70 | provisionedThroughput['ReadCapacityUnits'] = int(provisionedThroughput['ReadCapacityUnits']) 71 | provisionedThroughput['WriteCapacityUnits'] = int(provisionedThroughput['WriteCapacityUnits']) 72 | 73 | return " --global-secondary-indexes '" + json.JSONEncoder(sort_keys=True).encode(globalSecondaryIndexes) + "'" 74 | else: 75 | return "" 76 | 77 | def toCLI(self, region, endpoint_url): 78 | return ''.join(('aws dynamodb create-table --region ', 79 | region, 80 | ' --endpoint-url ', 81 | endpoint_url, 82 | self.tableName(), 83 | self.attributeDefinitions(), 84 | self.keySchema(), 85 | self.localSecondaryIndexes(), 86 | self.globalSecondaryIndexes(), 87 | self.provisionedThroughput(), 88 | "\n")) 89 | 90 | 91 | -------------------------------------------------------------------------------- /test/data/sample.template: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion" : "2010-09-09", 3 | "Resources" : { 4 | "myDynamoDBTable" : { 5 | "Type" : "AWS::DynamoDB::Table", 6 | "Properties" : { 7 | "AttributeDefinitions" : [ 8 | { 9 | "AttributeName" : "Album", 10 | "AttributeType" : "S" 11 | }, 12 | { 13 | "AttributeName" : "Artist", 14 | "AttributeType" : "S" 15 | }, 16 | { 17 | "AttributeName" : "Sales", 18 | "AttributeType" : "N" 19 | } 20 | ], 21 | "KeySchema" : [ 22 | { 23 | "AttributeName" : "Album", 24 | "KeyType" : "HASH" 25 | }, 26 | { 27 | "AttributeName" : "Artist", 28 | "KeyType" : "RANGE" 29 | } 30 | ], 31 | "ProvisionedThroughput" : { 32 | "ReadCapacityUnits" : "5", 33 | "WriteCapacityUnits" : "5" 34 | }, 35 | "TableName" : "myTableName", 36 | "GlobalSecondaryIndexes" : [{ 37 | "IndexName" : "myGSI", 38 | "KeySchema" : [ 39 | { 40 | "AttributeName" : "Sales", 41 | "KeyType" : "HASH" 42 | }, 43 | { 44 | "AttributeName" : "Artist", 45 | "KeyType" : "RANGE" 46 | } 47 | ], 48 | "Projection" : { 49 | "NonKeyAttributes" : ["Album"], 50 | "ProjectionType" : "INCLUDE" 51 | }, 52 | "ProvisionedThroughput" : { 53 | "ReadCapacityUnits" : "5", 54 | "WriteCapacityUnits" : "5" 55 | } 56 | }], 57 | "LocalSecondaryIndexes" :[{ 58 | "IndexName" : "myLSI", 59 | "KeySchema" : [ 60 | { 61 | "AttributeName" : "Album", 62 | "KeyType" : "HASH" 63 | }, 64 | { 65 | "AttributeName" : "Sales", 66 | "KeyType" : "RANGE" 67 | } 68 | ], 69 | "Projection" : { 70 | "NonKeyAttributes" : ["Artist"], 71 | "ProjectionType" : "INCLUDE" 72 | } 73 | }] 74 | } 75 | }, 76 | "mySecondDDBTable" : { 77 | "Type" : "AWS::DynamoDB::Table", 78 | "DependsOn" : "myDynamoDBTable" , 79 | "Properties" : { 80 | "AttributeDefinitions" : [ 81 | { 82 | "AttributeName" : "ArtistId", 83 | "AttributeType" : "S" 84 | }, 85 | { 86 | "AttributeName" : "Concert", 87 | "AttributeType" : "S" 88 | }, 89 | { 90 | "AttributeName" : "TicketSales", 91 | "AttributeType" : "S" 92 | } 93 | ], 94 | "KeySchema" : [ 95 | { 96 | "AttributeName" : "ArtistId", 97 | "KeyType" : "HASH" 98 | }, 99 | { 100 | "AttributeName" : "Concert", 101 | "KeyType" : "RANGE" 102 | } 103 | ], 104 | "TableName": "myTableName2", 105 | "ProvisionedThroughput" : { 106 | "ReadCapacityUnits" : "5", 107 | "WriteCapacityUnits" : "5" 108 | }, 109 | "GlobalSecondaryIndexes" : [{ 110 | "IndexName" : "myGSI", 111 | "KeySchema" : [ 112 | { 113 | "AttributeName" : "TicketSales", 114 | "KeyType" : "HASH" 115 | } 116 | ], 117 | "Projection" : { 118 | "ProjectionType" : "KEYS_ONLY" 119 | }, 120 | "ProvisionedThroughput" : { 121 | "ReadCapacityUnits" : "5", 122 | "WriteCapacityUnits" : "5" 123 | } 124 | }] 125 | } 126 | } 127 | 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cloud Formation parser for DynamoDB local 2 | 3 | This script converts the DynamoDB resources in a Amazon Web Services Cloud Formation template to a series of [AWS Command Line Interface] commands. 4 | 5 | ## Prerequisites 6 | 7 | * [Python] 8 | * [AWS Command Line Interface] 9 | * [AWS DynamoDB Local] 10 | 11 | You must also configure the default credentials for use with [AWS Command Line Interface] 12 | ```sh 13 | $ aws configure 14 | ``` 15 | 16 | ## Usage 17 | ```sh 18 | $ python ./parse.py [-h] [--region ] [--endpoint-url ] 19 | ``` 20 | - region - The AWS region simulate in DynamoDB Local (default: us-east-1) 21 | - endpoint-url - The DynamoDB Local endpoint URL (default: http://localhost:8000) 22 | - filename - The AWS CloudFormation template to convert 23 | 24 | ## Running the tests 25 | 26 | ```sh 27 | python -m unittest discover 28 | ``` 29 | 30 | ## Putting it all together 31 | 32 | Assuming you have DynamoDB Local running on port 8000 you can do the following to build and exexcute the script in one command: 33 | 34 | ```sh 35 | python ./parse.py test/data/sample.template | sh 36 | ``` 37 | 38 | 39 | ## Example 40 | 41 | Consider the following sample cloud formation template with two tables defined. There are two tables: 42 | * myTableName 43 | * myTableName2 44 | 45 | The second table depends on the first so it will be constructed after the first. 46 | 47 | ```sh 48 | python ./parse.py test/data/sample.template 49 | ``` 50 | 51 | 52 | 53 | ```sh 54 | { 55 | "AWSTemplateFormatVersion" : "2010-09-09", 56 | "Resources" : { 57 | "myDynamoDBTable" : { 58 | "Type" : "AWS::DynamoDB::Table", 59 | "Properties" : { 60 | "AttributeDefinitions" : [ 61 | { 62 | "AttributeName" : "Album", 63 | "AttributeType" : "S" 64 | }, 65 | { 66 | "AttributeName" : "Artist", 67 | "AttributeType" : "S" 68 | }, 69 | { 70 | "AttributeName" : "Sales", 71 | "AttributeType" : "N" 72 | } 73 | ], 74 | "KeySchema" : [ 75 | { 76 | "AttributeName" : "Album", 77 | "KeyType" : "HASH" 78 | }, 79 | { 80 | "AttributeName" : "Artist", 81 | "KeyType" : "RANGE" 82 | } 83 | ], 84 | "ProvisionedThroughput" : { 85 | "ReadCapacityUnits" : "5", 86 | "WriteCapacityUnits" : "5" 87 | }, 88 | "TableName" : "myTableName", 89 | "GlobalSecondaryIndexes" : [{ 90 | "IndexName" : "myGSI", 91 | "KeySchema" : [ 92 | { 93 | "AttributeName" : "Sales", 94 | "KeyType" : "HASH" 95 | }, 96 | { 97 | "AttributeName" : "Artist", 98 | "KeyType" : "RANGE" 99 | } 100 | ], 101 | "Projection" : { 102 | "NonKeyAttributes" : ["Album"], 103 | "ProjectionType" : "INCLUDE" 104 | }, 105 | "ProvisionedThroughput" : { 106 | "ReadCapacityUnits" : "5", 107 | "WriteCapacityUnits" : "5" 108 | } 109 | }], 110 | "LocalSecondaryIndexes" :[{ 111 | "IndexName" : "myLSI", 112 | "KeySchema" : [ 113 | { 114 | "AttributeName" : "Album", 115 | "KeyType" : "HASH" 116 | }, 117 | { 118 | "AttributeName" : "Sales", 119 | "KeyType" : "RANGE" 120 | } 121 | ], 122 | "Projection" : { 123 | "NonKeyAttributes" : ["Artist"], 124 | "ProjectionType" : "INCLUDE" 125 | } 126 | }] 127 | } 128 | }, 129 | "mySecondDDBTable" : { 130 | "Type" : "AWS::DynamoDB::Table", 131 | "DependsOn" : "myDynamoDBTable" , 132 | "Properties" : { 133 | "AttributeDefinitions" : [ 134 | { 135 | "AttributeName" : "ArtistId", 136 | "AttributeType" : "S" 137 | }, 138 | { 139 | "AttributeName" : "Concert", 140 | "AttributeType" : "S" 141 | }, 142 | { 143 | "AttributeName" : "TicketSales", 144 | "AttributeType" : "S" 145 | } 146 | ], 147 | "KeySchema" : [ 148 | { 149 | "AttributeName" : "ArtistId", 150 | "KeyType" : "HASH" 151 | }, 152 | { 153 | "AttributeName" : "Concert", 154 | "KeyType" : "RANGE" 155 | } 156 | ], 157 | "TableName": "myTableName2", 158 | "ProvisionedThroughput" : { 159 | "ReadCapacityUnits" : "5", 160 | "WriteCapacityUnits" : "5" 161 | }, 162 | "GlobalSecondaryIndexes" : [{ 163 | "IndexName" : "myGSI", 164 | "KeySchema" : [ 165 | { 166 | "AttributeName" : "TicketSales", 167 | "KeyType" : "HASH" 168 | } 169 | ], 170 | "Projection" : { 171 | "ProjectionType" : "KEYS_ONLY" 172 | }, 173 | "ProvisionedThroughput" : { 174 | "ReadCapacityUnits" : "5", 175 | "WriteCapacityUnits" : "5" 176 | } 177 | }] 178 | } 179 | } 180 | 181 | } 182 | } 183 | 184 | ``` 185 | 186 | 187 | 188 | 189 | ```sh 190 | aws dynamodb create-table --region us-east-1 --endpoint-url http://localhost:8000 --table-name myTableName --attribute-definitions '[{"AttributeName": "Album", "AttributeType": "S"}, {"AttributeName": "Artist", "AttributeType": "S"}, {"AttributeName": "Sales", "AttributeType": "N"}]' --key-schema '[{"AttributeName": "Album", "KeyType": "HASH"}, {"AttributeName": "Artist", "KeyType": "RANGE"}]' --local-secondary-indexes '[{"IndexName": "myLSI", "KeySchema": [{"AttributeName": "Album", "KeyType": "HASH"}, {"AttributeName": "Sales", "KeyType": "RANGE"}], "Projection": {"NonKeyAttributes": ["Artist"], "ProjectionType": "INCLUDE"}}]' --global-secondary-indexes '[{"IndexName": "myGSI", "KeySchema": [{"AttributeName": "Sales", "KeyType": "HASH"}, {"AttributeName": "Artist", "KeyType": "RANGE"}], "Projection": {"NonKeyAttributes": ["Album"], "ProjectionType": "INCLUDE"}, "ProvisionedThroughput": {"ReadCapacityUnits": 5, "WriteCapacityUnits": 5}}]' --provisioned-throughput '{"ReadCapacityUnits": 5, "WriteCapacityUnits": 5}' 191 | 192 | aws dynamodb create-table --region us-east-1 --endpoint-url http://localhost:8000 --table-name myTableName2 --attribute-definitions '[{"AttributeName": "ArtistId", "AttributeType": "S"}, {"AttributeName": "Concert", "AttributeType": "S"}, {"AttributeName": "TicketSales", "AttributeType": "S"}]' --key-schema '[{"AttributeName": "ArtistId", "KeyType": "HASH"}, {"AttributeName": "Concert", "KeyType": "RANGE"}]' --global-secondary-indexes '[{"IndexName": "myGSI", "KeySchema": [{"AttributeName": "TicketSales", "KeyType": "HASH"}], "Projection": {"ProjectionType": "KEYS_ONLY"}, "ProvisionedThroughput": {"ReadCapacityUnits": 5, "WriteCapacityUnits": 5}}]' --provisioned-throughput '{"ReadCapacityUnits": 5, "WriteCapacityUnits": 5}' 193 | 194 | ``` 195 | ## Known Issues 196 | 197 | This script would require further modification to support parameterised DynamoDB resources in a Cloud Formation template. 198 | 199 | [AWS Command Line Interface]:http://aws.amazon.com/cli/ 200 | [Python]:https://www.python.org/ 201 | [AWS DynamoDB Local]:http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Tools.DynamoDBLocal.html 202 | 203 | --------------------------------------------------------------------------------