dyna 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +9 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +41 -0
- data/Rakefile +6 -0
- data/bin/dyna +191 -0
- data/dyna.gemspec +32 -0
- data/lib/dyna/client.rb +89 -0
- data/lib/dyna/dsl/converter.rb +87 -0
- data/lib/dyna/dsl/dynamo_db.rb +31 -0
- data/lib/dyna/dsl/table.rb +112 -0
- data/lib/dyna/dsl.rb +54 -0
- data/lib/dyna/exporter.rb +105 -0
- data/lib/dyna/ext/hash-ext.rb +12 -0
- data/lib/dyna/ext/string-ext.rb +25 -0
- data/lib/dyna/filterable.rb +21 -0
- data/lib/dyna/logger.rb +30 -0
- data/lib/dyna/template_helper.rb +20 -0
- data/lib/dyna/utils.rb +17 -0
- data/lib/dyna/version.rb +3 -0
- data/lib/dyna/wrapper/dynamo_db_wrapper.rb +31 -0
- data/lib/dyna/wrapper/table.rb +205 -0
- data/lib/dyna.rb +27 -0
- metadata +181 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 468dd97763a9c340c77d46800af940b47bc6ac22
|
4
|
+
data.tar.gz: 6f446670b9ece4b36d7056dd0c2e3d61f380ebdd
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 011fd23c5040bb08d12e090669231e1db2de7ec899993456ad603e26c44080f345bad620ef86ad38a63569d9575b0301eacd5ac955a759760cb2787a5fd850ed
|
7
|
+
data.tar.gz: e336f511705841d336aabd8c3599a447bc3323aba06f300caa78f41d853eeaa93e83849d0d80968cc43f61ef30ba4f3e6a8e7dd39b2e4596047e4352af48e6f4
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2017 shinya-watanabe
|
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
|
13
|
+
all 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
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
# Dyna
|
2
|
+
|
3
|
+
Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/dyna`. To experiment with that code, run `bin/console` for an interactive prompt.
|
4
|
+
|
5
|
+
TODO: Delete this and the text above, and describe your gem
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'dyna'
|
13
|
+
```
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install dyna
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
TODO: Write usage instructions here
|
26
|
+
|
27
|
+
## Development
|
28
|
+
|
29
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
30
|
+
|
31
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
32
|
+
|
33
|
+
## Contributing
|
34
|
+
|
35
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/dyna.
|
36
|
+
|
37
|
+
|
38
|
+
## License
|
39
|
+
|
40
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
41
|
+
|
data/Rakefile
ADDED
data/bin/dyna
ADDED
@@ -0,0 +1,191 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$: << File.expand_path("#{File.dirname __FILE__}/../lib")
|
3
|
+
require 'rubygems'
|
4
|
+
require 'dyna'
|
5
|
+
require 'optparse'
|
6
|
+
require 'pry-byebug'
|
7
|
+
|
8
|
+
Version = Dyna::VERSION
|
9
|
+
|
10
|
+
mode = nil
|
11
|
+
file = 'Dynafile'
|
12
|
+
output_file = '-'
|
13
|
+
split = false
|
14
|
+
MAGIC_COMMENT = <<-EOS
|
15
|
+
# -*- mode: ruby -*-
|
16
|
+
# vi: set ft=ruby :
|
17
|
+
EOS
|
18
|
+
|
19
|
+
options = {
|
20
|
+
:dry_run => false,
|
21
|
+
:format => :ruby,
|
22
|
+
:color => true,
|
23
|
+
:debug => false,
|
24
|
+
}
|
25
|
+
|
26
|
+
ARGV.options do |opt|
|
27
|
+
begin
|
28
|
+
access_key = nil
|
29
|
+
secret_key = nil
|
30
|
+
region = nil
|
31
|
+
profile_name = nil
|
32
|
+
credentials_path = nil
|
33
|
+
format_passed = false
|
34
|
+
role_arn = nil
|
35
|
+
serial_number = nil
|
36
|
+
token_code = nil
|
37
|
+
role_session_name = nil
|
38
|
+
|
39
|
+
opt.on('-p', '--profile PROFILE_NAME') {|v| profile_name = v }
|
40
|
+
opt.on('', '--role_arn ROLE_ARN') {|v| role_arn = v }
|
41
|
+
opt.on('', '--serial_number SERIAL_NUMBER') {|v| serial_number = v }
|
42
|
+
opt.on('', '--token_code TOKEN_CODE') {|v| token_code = v }
|
43
|
+
opt.on('', '--role_session_name ROLE_SESSION_NAME') {|v| role_session_name = v }
|
44
|
+
opt.on('' , '--credentials-path PATH') {|v| credentials_path = v }
|
45
|
+
opt.on('-k', '--access-key ACCESS_KEY') {|v| access_key = v }
|
46
|
+
opt.on('-s', '--secret-key SECRET_KEY') {|v| secret_key = v }
|
47
|
+
opt.on('-r', '--region REGION') {|v| region = v }
|
48
|
+
opt.on('-a', '--apply') {|v| mode = :apply }
|
49
|
+
opt.on('-f', '--file FILE') {|v| file = v }
|
50
|
+
opt.on('', '--table_names TABLE_NAMES', Array) {|v| options[:table_names] = v }
|
51
|
+
opt.on('', '--exclude_table_names TABLE_NAMES', Array) {|v| options[:exclude_table_names] = v }
|
52
|
+
opt.on('', '--dry-run') {|v| options[:dry_run] = true }
|
53
|
+
opt.on('-e', '--export') {|v| mode = :export}
|
54
|
+
opt.on('-o', '--output FILE') {|v| output_file = v }
|
55
|
+
opt.on('', '--split') {|v| split = true }
|
56
|
+
opt.on('', '--split-more') {|v| split = :more }
|
57
|
+
opt.on('' , '--no-color') { options[:color] = false }
|
58
|
+
opt.on('' , '--debug') { options[:debug] = true }
|
59
|
+
opt.parse!
|
60
|
+
|
61
|
+
aws_opts = {}
|
62
|
+
if access_key and secret_key
|
63
|
+
aws_opts = {
|
64
|
+
:access_key_id => access_key,
|
65
|
+
:secret_access_key => secret_key,
|
66
|
+
}
|
67
|
+
elsif profile_name or credentials_path
|
68
|
+
credentials_opts = {}
|
69
|
+
credentials_opts[:profile_name] = profile_name if profile_name
|
70
|
+
credentials_opts[:path] = credentials_path if credentials_path
|
71
|
+
provider = AWS::Core::CredentialProviders::SharedCredentialFileProvider.new(credentials_opts)
|
72
|
+
aws_opts[:credential_provider] = provider
|
73
|
+
elsif role_arn and serial_number
|
74
|
+
credentials = Aws::AssumeRoleCredentials.new(
|
75
|
+
client: Aws::STS::Client.new,
|
76
|
+
role_arn: role_arn,
|
77
|
+
role_session_name: role_session_name,
|
78
|
+
serial_number: serial_number,
|
79
|
+
token_code: token_code,
|
80
|
+
)
|
81
|
+
aws_opts[:credentials] = credentials
|
82
|
+
elsif (access_key and !secret_key) or (!access_key and secret_key) or mode.nil?
|
83
|
+
puts opt.help
|
84
|
+
exit 1
|
85
|
+
end
|
86
|
+
|
87
|
+
aws_opts[:region] = region if region
|
88
|
+
Aws.config.update(aws_opts)
|
89
|
+
|
90
|
+
# Remap groups to exclude to regular expressions (if they're surrounded by '/')
|
91
|
+
if options[:exclude_table_names]
|
92
|
+
options[:exclude_table_names].map! do |name|
|
93
|
+
name =~ /\A\/(.*)\/\z/ ? Regexp.new($1) : Regexp.new("\A#{Regexp.escape(name)}\z")
|
94
|
+
end
|
95
|
+
end
|
96
|
+
rescue => e
|
97
|
+
$stderr.puts("[ERROR] #{e.message}")
|
98
|
+
exit 1
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
String.colorize = options[:color]
|
103
|
+
|
104
|
+
if options[:debug]
|
105
|
+
Aws.config.update({
|
106
|
+
:http_wire_trace => true,
|
107
|
+
:logger => Dyna::Logger.instance,
|
108
|
+
})
|
109
|
+
end
|
110
|
+
|
111
|
+
begin
|
112
|
+
logger = Dyna::Logger.instance
|
113
|
+
logger.set_debug(options[:debug])
|
114
|
+
client = Dyna::Client.new(options)
|
115
|
+
|
116
|
+
case mode
|
117
|
+
when :export
|
118
|
+
if split
|
119
|
+
logger.info('Export Table')
|
120
|
+
|
121
|
+
output_file = 'Dynafile' if output_file == '-'
|
122
|
+
requires = []
|
123
|
+
base_dir = File.dirname(output_file)
|
124
|
+
|
125
|
+
client.export(options) do |exported, converter|
|
126
|
+
write_table_file = proc do |table_file, tables|
|
127
|
+
requires << table_file
|
128
|
+
|
129
|
+
logger.info(" write `#{table_file}`")
|
130
|
+
FileUtils.mkdir_p(File.dirname(table_file))
|
131
|
+
|
132
|
+
open(table_file, 'wb') do |f|
|
133
|
+
f.puts MAGIC_COMMENT
|
134
|
+
f.puts converter.call(tables)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
exported.each do |table_name, tables|
|
139
|
+
table_file = File.join(base_dir, "#{Aws::DynamoDB::Client.new.config.region}/#{table_name}.dyna")
|
140
|
+
write_table_file.call(table_file, table_name => tables)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
logger.info(" write `#{output_file}`")
|
145
|
+
open(output_file, 'wb') do |f|
|
146
|
+
f.puts MAGIC_COMMENT
|
147
|
+
|
148
|
+
requires.each do |table_file|
|
149
|
+
table_file.sub!(%r|\A#{Regexp.escape base_dir}/?|, '')
|
150
|
+
f.puts "require '#{table_file}'"
|
151
|
+
end
|
152
|
+
end
|
153
|
+
else
|
154
|
+
exported = client.export(options)
|
155
|
+
|
156
|
+
if output_file == '-'
|
157
|
+
logger.info('# Export Table') if options[:format] == :ruby
|
158
|
+
puts exported
|
159
|
+
else
|
160
|
+
logger.info("Export Table to `#{output_file}`")
|
161
|
+
|
162
|
+
open(output_file, 'wb') do |f|
|
163
|
+
f.puts MAGIC_COMMENT if options[:format] == :ruby
|
164
|
+
f.puts exported
|
165
|
+
end
|
166
|
+
end
|
167
|
+
end
|
168
|
+
when :apply
|
169
|
+
unless File.exist?(file)
|
170
|
+
raise "No Dynafile found (looking for: #{file})"
|
171
|
+
end
|
172
|
+
|
173
|
+
msg = "Apply `#{file}` to DynamoDB"
|
174
|
+
msg << ' (dry-run)' if options[:dry_run]
|
175
|
+
logger.info(msg)
|
176
|
+
|
177
|
+
updated = client.apply(file)
|
178
|
+
|
179
|
+
logger.info('No change'.intense_blue) unless updated
|
180
|
+
else
|
181
|
+
raise 'must not happen'
|
182
|
+
end
|
183
|
+
rescue => e
|
184
|
+
if options[:debug]
|
185
|
+
raise e
|
186
|
+
else
|
187
|
+
$stderr.puts("[ERROR] #{e.message}".red)
|
188
|
+
$stderr.puts("#{e.backtrace.join("\n")}".red)
|
189
|
+
exit 1
|
190
|
+
end
|
191
|
+
end
|
data/dyna.gemspec
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'dyna/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "dyna"
|
8
|
+
spec.version = Dyna::VERSION
|
9
|
+
spec.authors = ["wata"]
|
10
|
+
spec.email = ["wata.gm@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Codenize DynamoDB table}
|
13
|
+
spec.description = %q{Manager DynamoDB table by DSL}
|
14
|
+
spec.homepage = 'https://github.com/wata-gh/dyna'
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
18
|
+
f.match(%r{^(test|spec|features)/})
|
19
|
+
end
|
20
|
+
spec.bindir = "bin"
|
21
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
22
|
+
spec.require_paths = ["lib"]
|
23
|
+
spec.add_dependency 'aws-sdk', '~> 2'
|
24
|
+
spec.add_dependency 'term-ansicolor', '~> 1.4'
|
25
|
+
spec.add_dependency 'diffy', '~> 3.1'
|
26
|
+
spec.add_dependency 'hashie', '~> 3.4'
|
27
|
+
|
28
|
+
spec.add_development_dependency 'bundler', '~> 1.13'
|
29
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
30
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
31
|
+
spec.add_development_dependency 'pry-byebug', '~> 3.4'
|
32
|
+
end
|
data/lib/dyna/client.rb
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
module Dyna
|
2
|
+
class Client
|
3
|
+
include Logger::ClientHelper
|
4
|
+
include Filterable
|
5
|
+
|
6
|
+
def initialize(options = {})
|
7
|
+
@options = OpenStruct.new(options)
|
8
|
+
@options_hash = options
|
9
|
+
@options.ddb = Aws::DynamoDB::Client.new
|
10
|
+
end
|
11
|
+
|
12
|
+
def apply(file)
|
13
|
+
walk(file)
|
14
|
+
end
|
15
|
+
|
16
|
+
def export(options = {})
|
17
|
+
exported = Exporter.export(@options.ddb, @options)
|
18
|
+
|
19
|
+
converter = proc do |src|
|
20
|
+
DSL.convert(@options.ddb.config.region, src)
|
21
|
+
end
|
22
|
+
|
23
|
+
if block_given?
|
24
|
+
yield(exported, converter)
|
25
|
+
else
|
26
|
+
converter.call(exported)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
def load_file(file)
|
32
|
+
if file.kind_of?(String)
|
33
|
+
open(file) do |f|
|
34
|
+
parse(f.read, file)
|
35
|
+
end
|
36
|
+
elsif file.respond_to?(:read)
|
37
|
+
parse(file.read, file.path)
|
38
|
+
else
|
39
|
+
raise TypeError, "can't load #{file}"
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def parse(src, path)
|
44
|
+
DSL.define(src, path).result
|
45
|
+
end
|
46
|
+
|
47
|
+
def walk(file)
|
48
|
+
dsl = load_file(file)
|
49
|
+
dsl_ddbs = dsl.ddbs
|
50
|
+
ddb_wrapper = DynamoDBWrapper.new(@options.ddb, @options)
|
51
|
+
|
52
|
+
dsl_ddbs.each do |region, ddb_dsl|
|
53
|
+
walk_ddb(ddb_dsl, ddb_wrapper) if @options.ddb.config.region == region
|
54
|
+
end
|
55
|
+
|
56
|
+
ddb_wrapper.updated?
|
57
|
+
end
|
58
|
+
|
59
|
+
def walk_ddb(ddb_dsl, ddb_wrapper)
|
60
|
+
table_list_dsl = ddb_dsl.tables.group_by(&:table_name).each_with_object({}) do |(k, v), h|
|
61
|
+
h[k] = v.first unless should_skip(k)
|
62
|
+
end
|
63
|
+
table_list_aws = ddb_wrapper.tables.group_by(&:table_name).each_with_object({}) do |(k, v), h|
|
64
|
+
h[k] = v.first unless should_skip(k)
|
65
|
+
end
|
66
|
+
|
67
|
+
table_list_dsl.each do |name, table_dsl|
|
68
|
+
unless table_list_aws[name]
|
69
|
+
result = ddb_wrapper.create(table_dsl)
|
70
|
+
if result
|
71
|
+
table_list_aws[name] = DynamoDBWrapper::Table.new(
|
72
|
+
@options.ddb,
|
73
|
+
result.table_description,
|
74
|
+
@options,
|
75
|
+
)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
table_list_dsl.each do |name, table_dsl|
|
81
|
+
table_aws = table_list_aws.delete(name)
|
82
|
+
next unless table_aws # only dry-run and should be created
|
83
|
+
table_aws.update(table_dsl) unless table_aws.eql?(table_dsl)
|
84
|
+
end
|
85
|
+
|
86
|
+
table_list_aws.values.each(&:delete)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module Dyna
|
2
|
+
class DSL
|
3
|
+
class Converter
|
4
|
+
class << self
|
5
|
+
def convert(region, exported)
|
6
|
+
self.new(region, exported).convert
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize(region, exported)
|
11
|
+
@region = region
|
12
|
+
@exported = exported
|
13
|
+
end
|
14
|
+
|
15
|
+
def convert
|
16
|
+
output_dynamo_db
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
def output_dynamo_db
|
21
|
+
tables = @exported.map {|name, table|
|
22
|
+
output_table(name, table)
|
23
|
+
}.join("\n").strip
|
24
|
+
|
25
|
+
<<-EOS
|
26
|
+
dynamo_db "#{@region}" do
|
27
|
+
#{tables}
|
28
|
+
end
|
29
|
+
EOS
|
30
|
+
end
|
31
|
+
|
32
|
+
def output_table(name, table)
|
33
|
+
local_secondary_indexes = ''
|
34
|
+
global_secondary_indexes = ''
|
35
|
+
if table[:local_secondary_indexes]
|
36
|
+
local_secondary_indexes_tmpl = <<-EOS.chomp
|
37
|
+
<% table[:local_secondary_indexes].each do |index| %>
|
38
|
+
local_secondary_index <%= index[:index_name].inspect %> do
|
39
|
+
key_schema hash: <%= index[:key_schema][0][:attribute_name].inspect %>, range: <%= index[:key_schema][1][:attribute_name].inspect %><% if index[:projection] %>
|
40
|
+
projection projection_type: <%= index[:projection][:projection_type].inspect %><% end %>
|
41
|
+
end
|
42
|
+
<% end %>
|
43
|
+
EOS
|
44
|
+
local_secondary_indexes = ERB.new(local_secondary_indexes_tmpl).result(binding)
|
45
|
+
end
|
46
|
+
|
47
|
+
if table[:global_secondary_indexes]
|
48
|
+
p table[:global_secondary_indexes]
|
49
|
+
global_secondary_indexes_tmpl = <<-EOS.chomp
|
50
|
+
<% table[:global_secondary_indexes].each do |index| %>
|
51
|
+
global_secondary_index <%= index[:index_name].inspect %> do
|
52
|
+
key_schema hash: <%= index[:key_schema][0][:attribute_name].inspect %><% if index[:key_schema].size == 2 %>, range: <%= index[:key_schema][1][:attribute_name].inspect %><% end %><% if index[:projection] %>
|
53
|
+
projection projection_type: <%= index[:projection][:projection_type].inspect %><% end %>
|
54
|
+
provisioned_throughput read_capacity_units: <%= index[:provisioned_throughput][:read_capacity_units] %>, write_capacity_units: <%= index[:provisioned_throughput][:read_capacity_units] %>
|
55
|
+
end
|
56
|
+
<% end %>
|
57
|
+
EOS
|
58
|
+
global_secondary_indexes = ERB.new(global_secondary_indexes_tmpl).result(binding)
|
59
|
+
end
|
60
|
+
|
61
|
+
attribute_definitions_tmpl = <<-EOS.chomp
|
62
|
+
<% table[:attribute_definitions].each do |attr| %>
|
63
|
+
attribute_definition(
|
64
|
+
attribute_name: <%= attr[:attribute_name].inspect %>,
|
65
|
+
attribute_type: <%= attr[:attribute_type].inspect %>,
|
66
|
+
)
|
67
|
+
<% end %>
|
68
|
+
EOS
|
69
|
+
attribute_definitions = ERB.new(attribute_definitions_tmpl).result(binding)
|
70
|
+
<<-EOS
|
71
|
+
table "#{name}" do
|
72
|
+
key_schema(
|
73
|
+
hash: #{table[:key_schema][0][:attribute_name].inspect},
|
74
|
+
range: #{table[:key_schema][1][:attribute_name].inspect},
|
75
|
+
)
|
76
|
+
#{attribute_definitions}
|
77
|
+
provisioned_throughput(
|
78
|
+
read_capacity_units: #{table[:provisioned_throughput][:read_capacity_units]},
|
79
|
+
write_capacity_units: #{table[:provisioned_throughput][:write_capacity_units]},
|
80
|
+
)
|
81
|
+
#{local_secondary_indexes}#{global_secondary_indexes}
|
82
|
+
end
|
83
|
+
EOS
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Dyna
|
2
|
+
class DSL
|
3
|
+
class DynamoDB
|
4
|
+
include Dyna::TemplateHelper
|
5
|
+
|
6
|
+
attr_reader :result
|
7
|
+
|
8
|
+
def initialize(context, tables, &block)
|
9
|
+
@context = context
|
10
|
+
@result = OpenStruct.new({
|
11
|
+
:tables => tables,
|
12
|
+
})
|
13
|
+
|
14
|
+
instance_eval(&block)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
def table(name, &block)
|
19
|
+
if table_names.include?(name)
|
20
|
+
raise "Table `#{name}` is already defined"
|
21
|
+
end
|
22
|
+
|
23
|
+
@result.tables << Table.new(@context, name, &block).result
|
24
|
+
end
|
25
|
+
|
26
|
+
def table_names
|
27
|
+
@result.tables.map(&:table_name)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
module Dyna
|
2
|
+
class DSL
|
3
|
+
class DynamoDB
|
4
|
+
class Table
|
5
|
+
include Dyna::TemplateHelper
|
6
|
+
attr_reader :result
|
7
|
+
|
8
|
+
def initialize(context, table_name, &block)
|
9
|
+
@table_name = table_name
|
10
|
+
@context = context
|
11
|
+
|
12
|
+
@result = Hashie::Mash.new({
|
13
|
+
:table_name => table_name,
|
14
|
+
})
|
15
|
+
instance_eval(&block)
|
16
|
+
end
|
17
|
+
|
18
|
+
def key_schema(hash:, range: nil)
|
19
|
+
@result.key_schema = [{
|
20
|
+
attribute_name: hash,
|
21
|
+
key_type: 'HASH',
|
22
|
+
}]
|
23
|
+
|
24
|
+
if range
|
25
|
+
@result.key_schema << {
|
26
|
+
attribute_name: range,
|
27
|
+
key_type: 'RANGE',
|
28
|
+
}
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def attribute_definition(attribute_name:, attribute_type:)
|
33
|
+
@result.attribute_definitions ||= []
|
34
|
+
@result.attribute_definitions << {
|
35
|
+
attribute_name: attribute_name,
|
36
|
+
attribute_type: attribute_type,
|
37
|
+
}
|
38
|
+
end
|
39
|
+
|
40
|
+
def provisioned_throughput(read_capacity_units:, write_capacity_units:)
|
41
|
+
@result.provisioned_throughput = {
|
42
|
+
read_capacity_units: read_capacity_units,
|
43
|
+
write_capacity_units: write_capacity_units,
|
44
|
+
}
|
45
|
+
end
|
46
|
+
|
47
|
+
def stream_specification(stream_enabled:, stream_view_type: nil)
|
48
|
+
@result.stream_specification = {
|
49
|
+
stream_enabled: stream_enabled,
|
50
|
+
stream_view_type: stream_view_type,
|
51
|
+
}
|
52
|
+
end
|
53
|
+
|
54
|
+
def local_secondary_index(index_name, &block)
|
55
|
+
@result.local_secondary_indexes ||= []
|
56
|
+
index = LocalSecondaryIndex.new
|
57
|
+
index.instance_eval(&block)
|
58
|
+
@result.local_secondary_indexes << {
|
59
|
+
index_name: index_name,
|
60
|
+
}.merge(index.result.symbolize_keys)
|
61
|
+
end
|
62
|
+
|
63
|
+
def global_secondary_index(index_name, &block)
|
64
|
+
@result.global_secondary_indexes ||= []
|
65
|
+
index = GlobalSecondaryIndex.new
|
66
|
+
index.instance_eval(&block)
|
67
|
+
@result.global_secondary_indexes << {
|
68
|
+
index_name: index_name,
|
69
|
+
}.merge(index.result.symbolize_keys)
|
70
|
+
end
|
71
|
+
|
72
|
+
class LocalSecondaryIndex
|
73
|
+
attr_accessor :result
|
74
|
+
|
75
|
+
def initialize
|
76
|
+
@result = Hashie::Mash.new
|
77
|
+
end
|
78
|
+
|
79
|
+
def key_schema(hash:, range: nil)
|
80
|
+
@result.key_schema = [{
|
81
|
+
attribute_name: hash,
|
82
|
+
key_type: 'HASH',
|
83
|
+
}]
|
84
|
+
|
85
|
+
if range
|
86
|
+
@result.key_schema << {
|
87
|
+
attribute_name: range,
|
88
|
+
key_type: 'RANGE',
|
89
|
+
}
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def projection(projection_type:, non_key_attributes: nil)
|
94
|
+
@result.projection = {
|
95
|
+
projection_type: projection_type,
|
96
|
+
non_key_attributes: non_key_attributes,
|
97
|
+
}
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
class GlobalSecondaryIndex < LocalSecondaryIndex
|
102
|
+
def provisioned_throughput(read_capacity_units:, write_capacity_units:)
|
103
|
+
@result.provisioned_throughput = {
|
104
|
+
read_capacity_units: read_capacity_units,
|
105
|
+
write_capacity_units: write_capacity_units,
|
106
|
+
}
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|