Skip to content
This repository has been archived by the owner on Sep 9, 2022. It is now read-only.

Commit

Permalink
Merge pull request #440 from laxmiprasanna-gunna/dynamo-db
Browse files Browse the repository at this point in the history
Add Dynamo DB support
  • Loading branch information
dtan4 committed Apr 21, 2019
2 parents a81f297 + f9cf988 commit dcbc8b4
Show file tree
Hide file tree
Showing 8 changed files with 681 additions and 0 deletions.
1 change: 1 addition & 0 deletions README.md
Expand Up @@ -89,6 +89,7 @@ Commands:
terraforming dbpg # Database Parameter Group
terraforming dbsg # Database Security Group
terraforming dbsn # Database Subnet Group
terraforming ddb # Dynamo DB
terraforming ec2 # EC2
terraforming ecc # ElastiCache Cluster
terraforming ecsn # ElastiCache Subnet Group
Expand Down
2 changes: 2 additions & 0 deletions lib/terraforming.rb
@@ -1,5 +1,6 @@
require "aws-sdk-autoscaling"
require "aws-sdk-cloudwatch"
require "aws-sdk-dynamodb"
require "aws-sdk-ec2"
require "aws-sdk-efs"
require "aws-sdk-elasticache"
Expand Down Expand Up @@ -29,6 +30,7 @@
require "terraforming/resource/db_parameter_group"
require "terraforming/resource/db_security_group"
require "terraforming/resource/db_subnet_group"
require "terraforming/resource/dynamo_db"
require "terraforming/resource/ec2"
require "terraforming/resource/eip"
require "terraforming/resource/elasti_cache_cluster"
Expand Down
5 changes: 5 additions & 0 deletions lib/terraforming/cli.rb
Expand Up @@ -40,6 +40,11 @@ def dbsn
execute(Terraforming::Resource::DBSubnetGroup, options)
end

desc "ddb", "Dynamo DB"
def ddb
execute(Terraforming::Resource::DynamoDb, options)
end

desc "ec2", "EC2"
def ec2
execute(Terraforming::Resource::EC2, options)
Expand Down
282 changes: 282 additions & 0 deletions lib/terraforming/resource/dynamo_db.rb
@@ -0,0 +1,282 @@
module Terraforming
module Resource
class DynamoDb
include Terraforming::Util
def self.tf(client: Aws::DynamoDB::Client.new)
self.new(client).tf
end

def self.tfstate(client: Aws::DynamoDB::Client.new)
self.new(client).tfstate
end

def initialize(client)
@client = client
end

def tf
apply_template(@client, "tf/dynamo_db")
end

def tfstate
tables.inject({}) do |resources, dynamo_db_table|
attributes = {
"arn" => dynamo_db_table["table_arn"],
"id" => dynamo_db_table["table_name"],
"name" => dynamo_db_table["table_name"],
"read_capacity" => dynamo_db_table["provisioned_throughput"]["read_capacity_units"].to_s,
"stream_arn" => dynamo_db_table["latest_stream_arn"].to_s,
"stream_label" => dynamo_db_table["latest_stream_label"].to_s,
"write_capacity" => dynamo_db_table["provisioned_throughput"]["write_capacity_units"].to_s
}

attributes.merge!(attribute_definitions(dynamo_db_table))
attributes.merge!(global_indexes(dynamo_db_table))
attributes.merge!(local_indexes(dynamo_db_table))
attributes.merge!(key_schema(dynamo_db_table))
attributes.merge!(point_in_time_summary(dynamo_db_table))
attributes.merge!(sse_description(dynamo_db_table))
attributes.merge!(stream_specification(dynamo_db_table))
attributes.merge!(tags_of(dynamo_db_table))
attributes.merge!(ttl_of(dynamo_db_table))

resources["aws_dynamodb_table.#{module_name_of(dynamo_db_table)}"] = {
"type" => "aws_dynamodb_table",
"primary" => {
"id" => dynamo_db_table.table_name,
"attributes" => attributes,
"meta" => {
"schema_version" => "1"
}
}
}
resources
end
end

private

def tables
tables = []
dynamo_db_tables.each do |table|
attributes = @client.describe_table({
table_name: table
}).table
tables << attributes
end
return tables
end

def attribute_definitions(dynamo_db_table)
attributes = { "attribute.#" => dynamo_db_table["attribute_definitions"].length.to_s}
dynamo_db_table["attribute_definitions"].each do |attr_defn|
attributes.merge!(attributes_definitions_of(attr_defn))
end
attributes
end

def attributes_definitions_of(attr_defn)
hashcode = attribute_hashcode(attr_defn)
attributes = {
"attribute.#{hashcode}.name" => attr_defn.attribute_name,
"attribute.#{hashcode}.type" => attr_defn.attribute_type,
}
attributes
end

def attribute_hashcode(attr_defn)
hashcode = Zlib.crc32(attr_defn.attribute_name+"-")
end

def global_indexes(dynamo_db_table)
attributes = {}
if dynamo_db_table["global_secondary_indexes"]
attributes = { "global_secondary_index.#" => dynamo_db_table["global_secondary_indexes"].length.to_s}
dynamo_db_table["global_secondary_indexes"].each do |global_sec_index|
attributes.merge!(global_secondary_indexes_of(global_sec_index))
end
end
return attributes
end


def global_secondary_indexes_of(global_sec_index)
attributes = global_indexes_of(global_sec_index).merge!(global_index_non_key_attributes(global_sec_index))
end

def global_indexes_of(global_sec_index)
hashcode = global_index_hashcode(global_sec_index)
attributes = {
"global_secondary_index.#{hashcode}.hash_key" => find_key(global_sec_index,"HASH"),
"global_secondary_index.#{hashcode}.name" => global_sec_index.index_name,
"global_secondary_index.#{hashcode}.projection_type" => global_sec_index.projection.projection_type,
"global_secondary_index.#{hashcode}.range_key" => find_key(global_sec_index,"RANGE"),
"global_secondary_index.#{hashcode}.read_capacity" => global_sec_index.provisioned_throughput.read_capacity_units.to_s ,
"global_secondary_index.#{hashcode}.write_capacity" => global_sec_index.provisioned_throughput.write_capacity_units.to_s,
}
attributes
end

def find_key(index,key_type)
index["key_schema"].each do |schema|
if schema.key_type == key_type
return schema.attribute_name
else
return ""
end
end
end

def global_index_non_key_attributes(global_sec_index)
attributes = {}
if !global_sec_index["projection"]["non_key_attributes"].nil?
hashcode = global_index_hashcode(global_sec_index)
attributes = {"global_secondary_index.#{hashcode}.non_key_attributes.#" => global_sec_index["projection"]["non_key_attributes"].length.to_s}
(0..global_sec_index["projection"]["non_key_attributes"].length.to_i-1).each do |index|
attributes.merge!({"global_secondary_index.#{hashcode}.non_key_attributes.#{index}" => global_sec_index["projection"]["non_key_attributes"][index]})
end
end
attributes
end


def global_index_hashcode(global_sec_index)
Zlib.crc32(global_sec_index["index_name"]+"-")
end

def local_indexes(dynamo_db_table)
attributes = {}
if dynamo_db_table["local_secondary_indexes"]
attributes = {"local_secondary_index.#" => dynamo_db_table["local_secondary_indexes"].length.to_s}
dynamo_db_table["local_secondary_indexes"].each do |local_sec_index|
attributes.merge!(local_secondary_indexes_of(local_sec_index))
end
end
return attributes
end

def local_secondary_indexes_of(local_sec_index)
attributes = {}
hashcode = local_index_hashcode(local_sec_index)
attributes.merge!("local_secondary_index.#{hashcode}.range_key" => find_key(local_sec_index,"RANGE")) if !find_key(local_sec_index,"RANGE").empty?
attributes.merge!({
"local_secondary_index.#{hashcode}.name" => local_sec_index.index_name,
"local_secondary_index.#{hashcode}.projection_type" => local_sec_index.projection.projection_type,
})
attributes.merge!(local_index_non_key_attributes(local_sec_index))
attributes
end

def local_index_non_key_attributes(local_sec_index)
attributes = {}
if !local_sec_index["projection"]["non_key_attributes"].nil?
hashcode = local_index_hashcode(local_sec_index)
attributes = {"local_secondary_index.#{hashcode}.non_key_attributes.#" => local_sec_index["projection"]["non_key_attributes"].length.to_s}
(0..local_sec_index["projection"]["non_key_attributes"].length.to_i-1).each do |index|
attributes.merge!({"local_secondary_index.#{hashcode}.non_key_attributes.#{index}" => local_sec_index["projection"]["non_key_attributes"][index]})
end
end
attributes
end

def local_index_hashcode(local_index)
Zlib.crc32(local_index["index_name"]+"-")
end

def key_schema(dynamo_db_table)
attributes = {}
if dynamo_db_table["key_schema"]
attributes = {"key_schema.#" => dynamo_db_table["key_schema"].length.to_s}
if !find_key(dynamo_db_table,"HASH").empty?
attributes.merge!({"hash_key" => find_key(dynamo_db_table,"HASH")})
end
end
attributes
end

def point_in_time_summary(dynamo_db_table)
resp = @client.describe_continuous_backups({
table_name: dynamo_db_table["table_name"]
})
if resp.continuous_backups_description.point_in_time_recovery_description.point_in_time_recovery_status == "ENABLED"
attributes = {"point_in_time_recovery.#" => 1.to_s}
attributes.merge!({"point_in_time_recovery.0.enabled" => true.to_s})
else
attributes = {"point_in_time_recovery.#" => 0.to_s}
end
end

def sse_description(dynamo_db_table)
attributes = {}
if dynamo_db_table.sse_description
if dynamo_db_table.sse_description.status == "ENABLED"
attributes = {"server_side_encryption.#" => 1.to_s}
attributes.merge!({"server_side_encryption.0.enabled" => true.to_s})
end
else
attributes.merge!({"server_side_encryption.#" => 0.to_s})
end
attributes
end

def stream_specification(dynamo_db_table)
attributes = {}
if dynamo_db_table.stream_specification
attributes = {"stream_view_type" => dynamo_db_table.stream_specification.stream_view_type} if dynamo_db_table.stream_specification.stream_enabled
end
attributes
end

def ttl_of(dynamo_db_table)
attributes = {}
ttl = ttl_values(dynamo_db_table)
if !ttl.empty?
hashcode = ttl_hashcode(ttl.first)
attributes = {"ttl.#" => 1.to_s}
attributes["ttl.#{hashcode}.attribute_name"] = ttl.first
attributes["ttl.#{hashcode}.enabled"] = true.to_s
end
return attributes
end

def ttl_hashcode(attribute)
Zlib.crc32(attribute)
end

def tags_of(dynamo_db_table)
attributes = {}
tags = tags(dynamo_db_table)
if !tags.empty?
attributes = { "tags.%" => tags.length.to_s }
tags.each do |tag|
attributes["tags.#{tag.key}"] = tag.value
end
end
attributes
end

def dynamo_db_tables
a = @client.list_tables.map(&:table_names).flatten
end

def ttl_values(dynamo_db_table)
ttl = @client.describe_time_to_live({
table_name: dynamo_db_table.table_name
}).time_to_live_description
if ttl.time_to_live_status == "ENABLED"
return [ttl.attribute_name]
else
return []
end
end

def tags(dynamo_db_table)
resp = @client.list_tags_of_resource({resource_arn: dynamo_db_table.table_arn}).tags
end

def module_name_of(dynamo_db_table)
normalize_module_name(dynamo_db_table['table_name'])
end
end
end
end
66 changes: 66 additions & 0 deletions lib/terraforming/template/tf/dynamo_db.erb
@@ -0,0 +1,66 @@
<%- tables.each do |table| -%>
resource "aws_dynamodb_table" "<%= table.table_name -%>" {
name = "<%= table.table_name -%>"
read_capacity = <%= table.provisioned_throughput.read_capacity_units %>
write_capacity = <%= table.provisioned_throughput.write_capacity_units %>
<%- table.key_schema.each do |key| -%>
<%= key.key_type.downcase -%>_key = <%= key.attribute_name.inspect %>
<%- end %>
<%- table.attribute_definitions.each do |attribute| -%>
attribute {
name = "<%= attribute.attribute_name -%>"
type = "<%= attribute.attribute_type -%>"
}
<%- end -%>
<%- ttl_values(table).each do |attr| -%>
ttl {
attribute_name = <%= attr.inspect %>
enabled = true
}
<%- end -%>
<%- Array(table.global_secondary_indexes).each do |index| -%>
global_secondary_index {
name = "<%= index.index_name -%>"
<%- index.key_schema.each do |key| -%>
<%= key.key_type.downcase -%>_key = "<%= key.attribute_name -%>"
<%- end -%>
read_capacity = <%= index.provisioned_throughput.read_capacity_units %>
write_capacity = <%= index.provisioned_throughput.write_capacity_units %>
projection_type = "<%= index.projection.projection_type %>"
<%- keys = index.projection.non_key_attributes -%>
<%- if Array(keys).size > 0 -%>
non_key_attributes = <%= keys.inspect -%>
<%- end %>
}
<%- end -%>
<%- Array(table.local_secondary_indexes).each do |index| -%>
local_secondary_index {
name = "<%= index.index_name -%>"
<%- index.key_schema.each do |key| -%>
<%- if key.key_type.downcase == "range" -%>
<%= key.key_type.downcase -%>_key = "<%= key.attribute_name -%>"
<%- end -%>
<%- end -%>
projection_type = "<%= index.projection.projection_type -%>"
<%- keys = index.projection.non_key_attributes -%>
<%- if Array(keys).size > 0 -%>
non_key_attributes = <%= keys.inspect -%>
<%- end %>
}
<%- end -%>
<%- tags(table).each do |tag| -%>
tags {
<%= tag.key %> = "<%= tag.value -%>"
}
<%- end -%>
<%- if table.stream_specification -%>
stream_enabled = <%= table.stream_specification.stream_enabled %>
stream_view_type = <%= table.stream_specification.stream_view_type.inspect %>
<%- end -%>
<%- if table.sse_description -%>
server_side_encryption {
enabled = true
}
<%- end -%>
}
<%- end -%>
7 changes: 7 additions & 0 deletions spec/lib/terraforming/cli_spec.rb
Expand Up @@ -85,6 +85,13 @@ module Terraforming
it_behaves_like "CLI examples"
end

describe "ddb" do
let(:klass) { Terraforming::Resource::DynamoDb }
let(:command) { :ddb }

it_behaves_like "CLI examples"
end

describe "ec2" do
let(:klass) { Terraforming::Resource::EC2 }
let(:command) { :ec2 }
Expand Down

0 comments on commit dcbc8b4

Please sign in to comment.