Skip to content

Commit dcbc8b4

Browse files
authored
Merge pull request dtan4#440 from laxmiprasanna-gunna/dynamo-db
Add Dynamo DB support
2 parents a81f297 + f9cf988 commit dcbc8b4

File tree

8 files changed

+681
-0
lines changed

8 files changed

+681
-0
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ Commands:
8989
terraforming dbpg # Database Parameter Group
9090
terraforming dbsg # Database Security Group
9191
terraforming dbsn # Database Subnet Group
92+
terraforming ddb # Dynamo DB
9293
terraforming ec2 # EC2
9394
terraforming ecc # ElastiCache Cluster
9495
terraforming ecsn # ElastiCache Subnet Group

lib/terraforming.rb

+2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
require "aws-sdk-autoscaling"
22
require "aws-sdk-cloudwatch"
3+
require "aws-sdk-dynamodb"
34
require "aws-sdk-ec2"
45
require "aws-sdk-efs"
56
require "aws-sdk-elasticache"
@@ -29,6 +30,7 @@
2930
require "terraforming/resource/db_parameter_group"
3031
require "terraforming/resource/db_security_group"
3132
require "terraforming/resource/db_subnet_group"
33+
require "terraforming/resource/dynamo_db"
3234
require "terraforming/resource/ec2"
3335
require "terraforming/resource/eip"
3436
require "terraforming/resource/elasti_cache_cluster"

lib/terraforming/cli.rb

+5
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,11 @@ def dbsn
4040
execute(Terraforming::Resource::DBSubnetGroup, options)
4141
end
4242

43+
desc "ddb", "Dynamo DB"
44+
def ddb
45+
execute(Terraforming::Resource::DynamoDb, options)
46+
end
47+
4348
desc "ec2", "EC2"
4449
def ec2
4550
execute(Terraforming::Resource::EC2, options)
+282
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,282 @@
1+
module Terraforming
2+
module Resource
3+
class DynamoDb
4+
include Terraforming::Util
5+
def self.tf(client: Aws::DynamoDB::Client.new)
6+
self.new(client).tf
7+
end
8+
9+
def self.tfstate(client: Aws::DynamoDB::Client.new)
10+
self.new(client).tfstate
11+
end
12+
13+
def initialize(client)
14+
@client = client
15+
end
16+
17+
def tf
18+
apply_template(@client, "tf/dynamo_db")
19+
end
20+
21+
def tfstate
22+
tables.inject({}) do |resources, dynamo_db_table|
23+
attributes = {
24+
"arn" => dynamo_db_table["table_arn"],
25+
"id" => dynamo_db_table["table_name"],
26+
"name" => dynamo_db_table["table_name"],
27+
"read_capacity" => dynamo_db_table["provisioned_throughput"]["read_capacity_units"].to_s,
28+
"stream_arn" => dynamo_db_table["latest_stream_arn"].to_s,
29+
"stream_label" => dynamo_db_table["latest_stream_label"].to_s,
30+
"write_capacity" => dynamo_db_table["provisioned_throughput"]["write_capacity_units"].to_s
31+
}
32+
33+
attributes.merge!(attribute_definitions(dynamo_db_table))
34+
attributes.merge!(global_indexes(dynamo_db_table))
35+
attributes.merge!(local_indexes(dynamo_db_table))
36+
attributes.merge!(key_schema(dynamo_db_table))
37+
attributes.merge!(point_in_time_summary(dynamo_db_table))
38+
attributes.merge!(sse_description(dynamo_db_table))
39+
attributes.merge!(stream_specification(dynamo_db_table))
40+
attributes.merge!(tags_of(dynamo_db_table))
41+
attributes.merge!(ttl_of(dynamo_db_table))
42+
43+
resources["aws_dynamodb_table.#{module_name_of(dynamo_db_table)}"] = {
44+
"type" => "aws_dynamodb_table",
45+
"primary" => {
46+
"id" => dynamo_db_table.table_name,
47+
"attributes" => attributes,
48+
"meta" => {
49+
"schema_version" => "1"
50+
}
51+
}
52+
}
53+
resources
54+
end
55+
end
56+
57+
private
58+
59+
def tables
60+
tables = []
61+
dynamo_db_tables.each do |table|
62+
attributes = @client.describe_table({
63+
table_name: table
64+
}).table
65+
tables << attributes
66+
end
67+
return tables
68+
end
69+
70+
def attribute_definitions(dynamo_db_table)
71+
attributes = { "attribute.#" => dynamo_db_table["attribute_definitions"].length.to_s}
72+
dynamo_db_table["attribute_definitions"].each do |attr_defn|
73+
attributes.merge!(attributes_definitions_of(attr_defn))
74+
end
75+
attributes
76+
end
77+
78+
def attributes_definitions_of(attr_defn)
79+
hashcode = attribute_hashcode(attr_defn)
80+
attributes = {
81+
"attribute.#{hashcode}.name" => attr_defn.attribute_name,
82+
"attribute.#{hashcode}.type" => attr_defn.attribute_type,
83+
}
84+
attributes
85+
end
86+
87+
def attribute_hashcode(attr_defn)
88+
hashcode = Zlib.crc32(attr_defn.attribute_name+"-")
89+
end
90+
91+
def global_indexes(dynamo_db_table)
92+
attributes = {}
93+
if dynamo_db_table["global_secondary_indexes"]
94+
attributes = { "global_secondary_index.#" => dynamo_db_table["global_secondary_indexes"].length.to_s}
95+
dynamo_db_table["global_secondary_indexes"].each do |global_sec_index|
96+
attributes.merge!(global_secondary_indexes_of(global_sec_index))
97+
end
98+
end
99+
return attributes
100+
end
101+
102+
103+
def global_secondary_indexes_of(global_sec_index)
104+
attributes = global_indexes_of(global_sec_index).merge!(global_index_non_key_attributes(global_sec_index))
105+
end
106+
107+
def global_indexes_of(global_sec_index)
108+
hashcode = global_index_hashcode(global_sec_index)
109+
attributes = {
110+
"global_secondary_index.#{hashcode}.hash_key" => find_key(global_sec_index,"HASH"),
111+
"global_secondary_index.#{hashcode}.name" => global_sec_index.index_name,
112+
"global_secondary_index.#{hashcode}.projection_type" => global_sec_index.projection.projection_type,
113+
"global_secondary_index.#{hashcode}.range_key" => find_key(global_sec_index,"RANGE"),
114+
"global_secondary_index.#{hashcode}.read_capacity" => global_sec_index.provisioned_throughput.read_capacity_units.to_s ,
115+
"global_secondary_index.#{hashcode}.write_capacity" => global_sec_index.provisioned_throughput.write_capacity_units.to_s,
116+
}
117+
attributes
118+
end
119+
120+
def find_key(index,key_type)
121+
index["key_schema"].each do |schema|
122+
if schema.key_type == key_type
123+
return schema.attribute_name
124+
else
125+
return ""
126+
end
127+
end
128+
end
129+
130+
def global_index_non_key_attributes(global_sec_index)
131+
attributes = {}
132+
if !global_sec_index["projection"]["non_key_attributes"].nil?
133+
hashcode = global_index_hashcode(global_sec_index)
134+
attributes = {"global_secondary_index.#{hashcode}.non_key_attributes.#" => global_sec_index["projection"]["non_key_attributes"].length.to_s}
135+
(0..global_sec_index["projection"]["non_key_attributes"].length.to_i-1).each do |index|
136+
attributes.merge!({"global_secondary_index.#{hashcode}.non_key_attributes.#{index}" => global_sec_index["projection"]["non_key_attributes"][index]})
137+
end
138+
end
139+
attributes
140+
end
141+
142+
143+
def global_index_hashcode(global_sec_index)
144+
Zlib.crc32(global_sec_index["index_name"]+"-")
145+
end
146+
147+
def local_indexes(dynamo_db_table)
148+
attributes = {}
149+
if dynamo_db_table["local_secondary_indexes"]
150+
attributes = {"local_secondary_index.#" => dynamo_db_table["local_secondary_indexes"].length.to_s}
151+
dynamo_db_table["local_secondary_indexes"].each do |local_sec_index|
152+
attributes.merge!(local_secondary_indexes_of(local_sec_index))
153+
end
154+
end
155+
return attributes
156+
end
157+
158+
def local_secondary_indexes_of(local_sec_index)
159+
attributes = {}
160+
hashcode = local_index_hashcode(local_sec_index)
161+
attributes.merge!("local_secondary_index.#{hashcode}.range_key" => find_key(local_sec_index,"RANGE")) if !find_key(local_sec_index,"RANGE").empty?
162+
attributes.merge!({
163+
"local_secondary_index.#{hashcode}.name" => local_sec_index.index_name,
164+
"local_secondary_index.#{hashcode}.projection_type" => local_sec_index.projection.projection_type,
165+
})
166+
attributes.merge!(local_index_non_key_attributes(local_sec_index))
167+
attributes
168+
end
169+
170+
def local_index_non_key_attributes(local_sec_index)
171+
attributes = {}
172+
if !local_sec_index["projection"]["non_key_attributes"].nil?
173+
hashcode = local_index_hashcode(local_sec_index)
174+
attributes = {"local_secondary_index.#{hashcode}.non_key_attributes.#" => local_sec_index["projection"]["non_key_attributes"].length.to_s}
175+
(0..local_sec_index["projection"]["non_key_attributes"].length.to_i-1).each do |index|
176+
attributes.merge!({"local_secondary_index.#{hashcode}.non_key_attributes.#{index}" => local_sec_index["projection"]["non_key_attributes"][index]})
177+
end
178+
end
179+
attributes
180+
end
181+
182+
def local_index_hashcode(local_index)
183+
Zlib.crc32(local_index["index_name"]+"-")
184+
end
185+
186+
def key_schema(dynamo_db_table)
187+
attributes = {}
188+
if dynamo_db_table["key_schema"]
189+
attributes = {"key_schema.#" => dynamo_db_table["key_schema"].length.to_s}
190+
if !find_key(dynamo_db_table,"HASH").empty?
191+
attributes.merge!({"hash_key" => find_key(dynamo_db_table,"HASH")})
192+
end
193+
end
194+
attributes
195+
end
196+
197+
def point_in_time_summary(dynamo_db_table)
198+
resp = @client.describe_continuous_backups({
199+
table_name: dynamo_db_table["table_name"]
200+
})
201+
if resp.continuous_backups_description.point_in_time_recovery_description.point_in_time_recovery_status == "ENABLED"
202+
attributes = {"point_in_time_recovery.#" => 1.to_s}
203+
attributes.merge!({"point_in_time_recovery.0.enabled" => true.to_s})
204+
else
205+
attributes = {"point_in_time_recovery.#" => 0.to_s}
206+
end
207+
end
208+
209+
def sse_description(dynamo_db_table)
210+
attributes = {}
211+
if dynamo_db_table.sse_description
212+
if dynamo_db_table.sse_description.status == "ENABLED"
213+
attributes = {"server_side_encryption.#" => 1.to_s}
214+
attributes.merge!({"server_side_encryption.0.enabled" => true.to_s})
215+
end
216+
else
217+
attributes.merge!({"server_side_encryption.#" => 0.to_s})
218+
end
219+
attributes
220+
end
221+
222+
def stream_specification(dynamo_db_table)
223+
attributes = {}
224+
if dynamo_db_table.stream_specification
225+
attributes = {"stream_view_type" => dynamo_db_table.stream_specification.stream_view_type} if dynamo_db_table.stream_specification.stream_enabled
226+
end
227+
attributes
228+
end
229+
230+
def ttl_of(dynamo_db_table)
231+
attributes = {}
232+
ttl = ttl_values(dynamo_db_table)
233+
if !ttl.empty?
234+
hashcode = ttl_hashcode(ttl.first)
235+
attributes = {"ttl.#" => 1.to_s}
236+
attributes["ttl.#{hashcode}.attribute_name"] = ttl.first
237+
attributes["ttl.#{hashcode}.enabled"] = true.to_s
238+
end
239+
return attributes
240+
end
241+
242+
def ttl_hashcode(attribute)
243+
Zlib.crc32(attribute)
244+
end
245+
246+
def tags_of(dynamo_db_table)
247+
attributes = {}
248+
tags = tags(dynamo_db_table)
249+
if !tags.empty?
250+
attributes = { "tags.%" => tags.length.to_s }
251+
tags.each do |tag|
252+
attributes["tags.#{tag.key}"] = tag.value
253+
end
254+
end
255+
attributes
256+
end
257+
258+
def dynamo_db_tables
259+
a = @client.list_tables.map(&:table_names).flatten
260+
end
261+
262+
def ttl_values(dynamo_db_table)
263+
ttl = @client.describe_time_to_live({
264+
table_name: dynamo_db_table.table_name
265+
}).time_to_live_description
266+
if ttl.time_to_live_status == "ENABLED"
267+
return [ttl.attribute_name]
268+
else
269+
return []
270+
end
271+
end
272+
273+
def tags(dynamo_db_table)
274+
resp = @client.list_tags_of_resource({resource_arn: dynamo_db_table.table_arn}).tags
275+
end
276+
277+
def module_name_of(dynamo_db_table)
278+
normalize_module_name(dynamo_db_table['table_name'])
279+
end
280+
end
281+
end
282+
end
+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
<%- tables.each do |table| -%>
2+
resource "aws_dynamodb_table" "<%= table.table_name -%>" {
3+
name = "<%= table.table_name -%>"
4+
read_capacity = <%= table.provisioned_throughput.read_capacity_units %>
5+
write_capacity = <%= table.provisioned_throughput.write_capacity_units %>
6+
<%- table.key_schema.each do |key| -%>
7+
<%= key.key_type.downcase -%>_key = <%= key.attribute_name.inspect %>
8+
<%- end %>
9+
<%- table.attribute_definitions.each do |attribute| -%>
10+
attribute {
11+
name = "<%= attribute.attribute_name -%>"
12+
type = "<%= attribute.attribute_type -%>"
13+
}
14+
<%- end -%>
15+
<%- ttl_values(table).each do |attr| -%>
16+
ttl {
17+
attribute_name = <%= attr.inspect %>
18+
enabled = true
19+
}
20+
<%- end -%>
21+
<%- Array(table.global_secondary_indexes).each do |index| -%>
22+
global_secondary_index {
23+
name = "<%= index.index_name -%>"
24+
<%- index.key_schema.each do |key| -%>
25+
<%= key.key_type.downcase -%>_key = "<%= key.attribute_name -%>"
26+
<%- end -%>
27+
read_capacity = <%= index.provisioned_throughput.read_capacity_units %>
28+
write_capacity = <%= index.provisioned_throughput.write_capacity_units %>
29+
projection_type = "<%= index.projection.projection_type %>"
30+
<%- keys = index.projection.non_key_attributes -%>
31+
<%- if Array(keys).size > 0 -%>
32+
non_key_attributes = <%= keys.inspect -%>
33+
<%- end %>
34+
}
35+
<%- end -%>
36+
<%- Array(table.local_secondary_indexes).each do |index| -%>
37+
local_secondary_index {
38+
name = "<%= index.index_name -%>"
39+
<%- index.key_schema.each do |key| -%>
40+
<%- if key.key_type.downcase == "range" -%>
41+
<%= key.key_type.downcase -%>_key = "<%= key.attribute_name -%>"
42+
<%- end -%>
43+
<%- end -%>
44+
projection_type = "<%= index.projection.projection_type -%>"
45+
<%- keys = index.projection.non_key_attributes -%>
46+
<%- if Array(keys).size > 0 -%>
47+
non_key_attributes = <%= keys.inspect -%>
48+
<%- end %>
49+
}
50+
<%- end -%>
51+
<%- tags(table).each do |tag| -%>
52+
tags {
53+
<%= tag.key %> = "<%= tag.value -%>"
54+
}
55+
<%- end -%>
56+
<%- if table.stream_specification -%>
57+
stream_enabled = <%= table.stream_specification.stream_enabled %>
58+
stream_view_type = <%= table.stream_specification.stream_view_type.inspect %>
59+
<%- end -%>
60+
<%- if table.sse_description -%>
61+
server_side_encryption {
62+
enabled = true
63+
}
64+
<%- end -%>
65+
}
66+
<%- end -%>

spec/lib/terraforming/cli_spec.rb

+7
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,13 @@ module Terraforming
8585
it_behaves_like "CLI examples"
8686
end
8787

88+
describe "ddb" do
89+
let(:klass) { Terraforming::Resource::DynamoDb }
90+
let(:command) { :ddb }
91+
92+
it_behaves_like "CLI examples"
93+
end
94+
8895
describe "ec2" do
8996
let(:klass) { Terraforming::Resource::EC2 }
9097
let(:command) { :ec2 }

0 commit comments

Comments
 (0)