fluent-plugin-dynamodb-alt 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 19e9db9d6797b1ac0b232a1190fe09498ea4502b
4
+ data.tar.gz: 21a3ce6f08bbcb44aa19f35bda31ecac38af0662
5
+ SHA512:
6
+ metadata.gz: de30d92832b0c478a585fb0aa8eff15cc2b640ddb10f74e920fd6788e98415e0ddc9f33888ef4c94b36a16e7cc4de50f06932e4b9300e00dfb8c4c0a74fc20b4
7
+ data.tar.gz: 9cdcde14528e63ac12d402a214d2843d108077a936ae824ecba828805fa7a71920e8ba3237d7c3377f2e4a8627869500c04f7edce5bbabf89461ff5bdafafe28
data/.gitignore ADDED
@@ -0,0 +1,16 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
15
+ test.rb
16
+ node_modules
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --require spec_helper
2
+ --colour
data/.travis.yml ADDED
@@ -0,0 +1,11 @@
1
+
2
+ language: ruby
3
+ rvm:
4
+ - 2.0.0
5
+ script:
6
+ - bundle install
7
+ - bundle exec rake
8
+ env:
9
+ global:
10
+ - secure: "ESwtSv6459PcZVWzwPbxIFxKVkm7F0BE30JlEgE2N4yWaMaWZfE9i2tsjh6cCB03I89sUuysHjpDtV/Hf7nUQC58s6q5VFPEex0RBBcbAC8ALLL+Ai6go6vPX2tqp0NP8v1OUdNDu7TtPQW2EFyBG75OJ1ga9UT1Q+hGnd2Xdmg="
11
+ - secure: "hHZGXwrDVpY7DtY6nSbkiBcIQlT4uvIlA0Xx/+AHHsEBgeAakSPVO8pVI3JrkpYNRbmYOXoj3TWs0Z0ROjPZdbOwKIaH3xzoFS61zk/oJbHume8wjhvWp8QGNukcbas+/3FuTyBc2CUDcy2zpuEx+p/PEYwccdS3LrTTSp4nYf0="
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in fluent-plugin-dynamodb-session.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Genki Sugawara
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,41 @@
1
+ # fluent-plugin-dynamodb-alt
2
+
3
+ Alternative fluent plugin to output to DynamoDB.
4
+
5
+ ## Features
6
+
7
+ * Use [PutItem](http://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_PutItem.html) Acrion
8
+ * Sort the records in the timestamp key.
9
+ * Aggregate the records in the primary key.
10
+ * Support [Expected constraint](http://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_PutItem.html#DDB-PutItem-request-Expected)
11
+
12
+ [![Gem Version](https://badge.fury.io/rb/fluent-plugin-dynamodb-alt.png)](http://badge.fury.io/rb/fluent-plugin-dynamodb-alt)
13
+ [![Build Status](https://travis-ci.org/winebarrel/fluent-plugin-dynamodb-alt.svg)](https://travis-ci.org/winebarrel/fluent-plugin-dynamodb-alt)
14
+
15
+ ## Installation
16
+
17
+ ```sh
18
+ bundle install
19
+ bundle exec rake install
20
+ ```
21
+
22
+ ## Configuration
23
+
24
+ ```
25
+ <match tag>
26
+ type dynamodb_alt
27
+ aws_key_id AKIAIOSFODNN7EXAMPLE
28
+ aws_sec_key wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
29
+ region ap-northeast-1
30
+ table_name my_table
31
+ timestamp_key timestamp
32
+ #concurrency 1
33
+
34
+ # see http://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_PutItem.html#DDB-PutItem-request-Expected
35
+ #expected id NULL,timestamp LT ${timestamp},key EQ "val"
36
+ #conditional_operator OR
37
+
38
+ #include_time_key false
39
+ #include_tag_key false
40
+ </match>
41
+ ```
data/Rakefile ADDED
@@ -0,0 +1,5 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new('spec')
5
+ task :default => :spec
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'fluent-plugin-dynamodb-alt'
7
+ spec.version = '0.1.0'
8
+ spec.authors = ['Genki Sugawara']
9
+ spec.email = ['sgwr_dts@yahoo.co.jp']
10
+ spec.summary = %q{Fluent plugin to output to DynamoDB.}
11
+ spec.description = %q{Fluent plugin to output to DynamoDB.}
12
+ spec.homepage = 'https://github.com/winebarrel/fluent-plugin-dynamodb-alt'
13
+ spec.license = 'MIT'
14
+
15
+ spec.files = `git ls-files -z`.split("\x0")
16
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ['lib']
19
+
20
+ spec.add_dependency 'fluentd'
21
+ spec.add_dependency 'aws-sdk-core', '>= 2.0.0.rc15'
22
+ spec.add_dependency 'parallel'
23
+ spec.add_development_dependency 'bundler'
24
+ spec.add_development_dependency 'rake', '~> 10.0'
25
+ spec.add_development_dependency 'rspec', '>= 3.0.0'
26
+ spec.add_development_dependency 'hashie'
27
+ spec.add_development_dependency 'ddbcli'
28
+ end
@@ -0,0 +1,202 @@
1
+ class Fluent::DynamodbAltOutput < Fluent::BufferedOutput
2
+ Fluent::Plugin.register_output('dynamodb_alt', self)
3
+
4
+ include Fluent::SetTimeKeyMixin
5
+ include Fluent::SetTagKeyMixin
6
+
7
+ unless method_defined?(:log)
8
+ define_method('log') { $log }
9
+ end
10
+
11
+ config_param :profile, :string, :default => nil
12
+ config_param :credentials_path, :string, :default => nil
13
+ config_param :aws_key_id, :string, :default => nil
14
+ config_param :aws_sec_key, :string, :default => nil
15
+ config_param :region, :string, :default => nil
16
+ config_param :table_name, :string
17
+ config_param :timestamp_key, :string
18
+ config_param :concurrency, :integer, :default => 1
19
+ config_param :expected, :string, :default => nil
20
+ config_param :conditional_operator, :string, :default => 'AND'
21
+
22
+ config_set_default :include_time_key, true
23
+ config_set_default :include_tag_key, true
24
+
25
+ def initialize
26
+ super
27
+ require 'aws-sdk-core'
28
+ require 'parallel'
29
+ end
30
+
31
+ def configure(conf)
32
+ super
33
+
34
+ aws_opts = {}
35
+
36
+ if @profile
37
+ credentials_opts = {:profile_name => @profile}
38
+ credentials_opts[:path] = @credentials_path if @credentials_path
39
+ credentials = Aws::SharedCredentials.new(credentials_opts)
40
+ aws_opts[:credentials] = credentials
41
+ end
42
+
43
+ aws_opts[:access_key_id] = @aws_key_id if @aws_key_id
44
+ aws_opts[:secret_access_key] = @aws_sec_key if @aws_sec_key
45
+ aws_opts[:region] = @region if @region
46
+
47
+ configure_aws(aws_opts)
48
+
49
+ client = create_client
50
+ table = client.describe_table(:table_name => @table_name)
51
+
52
+ table.table.key_schema.each do |attribute|
53
+ case attribute.key_type
54
+ when 'HASH'
55
+ @hash_key = attribute.attribute_name
56
+ when 'RANGE'
57
+ @range_key = attribute.attribute_name
58
+ else
59
+ raise 'must not happen'
60
+ end
61
+ end
62
+
63
+ if @expected
64
+ @expected = parse_expected(@expected)
65
+ log.info("dynamodb_alt expected: #{@expected.inspect}")
66
+ end
67
+ end
68
+
69
+ def start
70
+ super
71
+
72
+ @client = create_client
73
+ end
74
+
75
+ def format(tag, time, record)
76
+ [tag, time, record].to_msgpack
77
+ end
78
+
79
+ def write(chunk)
80
+ chunk = aggregate_records(chunk)
81
+ block = proc do |tag, time, record|
82
+ put_record(record)
83
+ end
84
+
85
+ if @concurrency > 1
86
+ Parallel.each(chunk, :in_threads => @concurrency, &block)
87
+ else
88
+ chunk.each(&block)
89
+ end
90
+ end
91
+
92
+ private
93
+
94
+ def configure_aws(options)
95
+ Aws.config.update(options)
96
+ end
97
+
98
+ def create_client
99
+ Aws::DynamoDB::Client.new
100
+ end
101
+
102
+ def put_record(record)
103
+ item = {
104
+ :table_name => @table_name,
105
+ :item => record
106
+ }
107
+
108
+ begin
109
+ if @expected
110
+ expected = create_expected(record)
111
+ return unless expected
112
+ item[:expected] = expected
113
+ item[:conditional_operator] = @conditional_operator if expected.length > 1
114
+ end
115
+
116
+ @client.put_item(item)
117
+ rescue Aws::DynamoDB::Errors::ConditionalCheckFailedException, Aws::DynamoDB::Errors::ValidationException => e
118
+ log.warn("#{e.message}: #{item.inspect}")
119
+ end
120
+ end
121
+
122
+ def validate_record(record)
123
+ if not record[@hash_key]
124
+ log.warn("Hash Key '#{@hash_key}' does not exist in the record: #{record.inspect}")
125
+ return false
126
+ end
127
+
128
+ if @range_key and not record[@range_key]
129
+ log.warn("Range Key '#{@range_key}' does not exist in the record: #{record.inspect}")
130
+ return false
131
+ end
132
+
133
+ if not record[@timestamp_key]
134
+ log.warn("Timestamp Key '#{@timestamp_key}' does not exist in the record: #{record.inspect}")
135
+ return false
136
+ end
137
+
138
+ return true
139
+ end
140
+
141
+ def parse_expected(expected)
142
+ expected.split(',').map do |expr|
143
+ key, op, val = expr.strip.split(/\s+/)
144
+
145
+ if val
146
+ if val =~ /\A\$\{(.+)\}\z/
147
+ record_key = $1.inspect
148
+ val = eval("proc {|record| record[#{record_key}] }")
149
+ else
150
+ begin
151
+ val = JSON.parse("[#{val}]").first if val
152
+ rescue JSON::ParserError => e
153
+ raise "Cannot parse the expected expression (#{expr}): #{e.message}"
154
+ end
155
+ end
156
+ end
157
+
158
+ [key, op, val]
159
+ end
160
+ end
161
+
162
+ def create_expected(record)
163
+ attrs = {}
164
+
165
+ @expected.map do |key, op, val|
166
+ attrs[key] = {:comparison_operator => op}
167
+
168
+ if val
169
+ if val.kind_of?(Proc)
170
+ record_val = val.call(record)
171
+
172
+ unless record_val
173
+ log.warn("Expected value does not exist in the record: #{record.inspect}")
174
+ return nil
175
+ end
176
+
177
+ attrs[key][:attribute_value_list] = [record_val]
178
+ else
179
+ attrs[key][:attribute_value_list] = [val]
180
+ end
181
+ end
182
+ end
183
+
184
+ return attrs
185
+ end
186
+
187
+ def aggregate_records(chunk)
188
+ chunk.enum_for(:msgpack_each).select {|tag, time, record|
189
+ validate_record(record)
190
+ }.chunk {|tag, time, record|
191
+ if @range_key
192
+ record.values_at(@hash_key, @range_key)
193
+ else
194
+ record[@hash_key]
195
+ end
196
+ }.map {|primary_key, records|
197
+ records.sort_by {|tag, time, record|
198
+ record[@timestamp_key]
199
+ }.last
200
+ }
201
+ end
202
+ end
@@ -0,0 +1,430 @@
1
+ describe Fluent::DynamodbAltOutput do
2
+ let(:time) {
3
+ Time.parse('2014-09-01 01:23:45 UTC').to_i
4
+ }
5
+
6
+ context('configure') {
7
+ it do
8
+ driver = create_driver
9
+ expect(driver.instance).to receive(:configure_aws).with(
10
+ :access_key_id => "AKIAIOSFODNN7EXAMPLE",
11
+ :secret_access_key => "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
12
+ :region => "ap-northeast-1")
13
+
14
+ expect(driver.instance).to receive(:create_client) {
15
+ client = double('client')
16
+ allow(client).to receive(:describe_table).with(:table_name => 'my_table') {
17
+ Hashie::Mash.new(:table => {
18
+ :key_schema => [
19
+ {:key_type => 'HASH', :attribute_name => 'hash_key'},
20
+ {:key_type => 'RANGE', :attribute_name => 'range_key'}
21
+ ]})
22
+ }
23
+ client
24
+ }
25
+
26
+ driver.configure(<<-EOS)
27
+ type dynamodb_alt
28
+ aws_key_id AKIAIOSFODNN7EXAMPLE
29
+ aws_sec_key wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
30
+ region ap-northeast-1
31
+ table_name my_table
32
+ timestamp_key timestamp
33
+ concurrency 2
34
+ conditional_operator OR
35
+ EOS
36
+
37
+ expect(driver.instance.aws_key_id ).to eq 'AKIAIOSFODNN7EXAMPLE'
38
+ expect(driver.instance.aws_sec_key ).to eq 'wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY'
39
+ expect(driver.instance.table_name ).to eq 'my_table'
40
+ expect(driver.instance.timestamp_key ).to eq 'timestamp'
41
+ expect(driver.instance.concurrency ).to eq 2
42
+ expect(driver.instance.conditional_operator).to eq 'OR'
43
+ expect(driver.instance.instance_variable_get(:@hash_key) ).to eq 'hash_key'
44
+ expect(driver.instance.instance_variable_get(:@range_key)).to eq 'range_key'
45
+ end
46
+
47
+ it do
48
+ driver = create_driver
49
+ allow(driver.instance).to receive(:configure_aws)
50
+
51
+ allow(driver.instance).to receive(:create_client) {
52
+ client = double('client')
53
+ allow(client).to receive(:describe_table) {
54
+ Hashie::Mash.new(:table => {
55
+ :key_schema => [
56
+ {:key_type => 'HASH', :attribute_name => 'hash_key'},
57
+ ]})
58
+ }
59
+ client
60
+ }
61
+
62
+ driver.configure(<<-EOS)
63
+ type dynamodb_alt
64
+ table_name my_table
65
+ timestamp_key timestamp
66
+ expected timestamp GE 0,key LT 100
67
+ EOS
68
+
69
+ expected = driver.instance.instance_variable_get(:@expected)
70
+ expect(expected).to eq [["timestamp", "GE", 0],["key", "LT", 100]]
71
+ end
72
+
73
+ it do
74
+ driver = create_driver
75
+ allow(driver.instance).to receive(:configure_aws)
76
+
77
+ allow(driver.instance).to receive(:create_client) {
78
+ client = double('client')
79
+ allow(client).to receive(:describe_table) {
80
+ Hashie::Mash.new(:table => {
81
+ :key_schema => [
82
+ {:key_type => 'HASH', :attribute_name => 'hash_key'},
83
+ ]})
84
+ }
85
+ client
86
+ }
87
+
88
+ driver.configure(<<-EOS)
89
+ type dynamodb_alt
90
+ table_name my_table
91
+ timestamp_key timestamp
92
+ expected id NULL,timestamp LT ${ts},key EQ ${k}
93
+ EOS
94
+
95
+ expected = driver.instance.instance_variable_get(:@expected)
96
+
97
+ expect(expected[0]).to eq ["id", "NULL", nil]
98
+
99
+ col1, op1, val1 = expected[1]
100
+ expect(col1).to eq 'timestamp'
101
+ expect(op1 ).to eq 'LT'
102
+ expect(val1.call('ts' => 1)).to eq 1
103
+
104
+ col2, op2, val2 = expected[2]
105
+ expect(col2).to eq 'key'
106
+ expect(op2 ).to eq 'EQ'
107
+ expect(val2.call('k' => '1')).to eq '1'
108
+ end
109
+
110
+ it do
111
+ driver = create_driver
112
+ allow(driver.instance).to receive(:configure_aws)
113
+
114
+ allow(driver.instance).to receive(:create_client) {
115
+ client = double('client')
116
+ allow(client).to receive(:describe_table) {
117
+ Hashie::Mash.new(:table => {
118
+ :key_schema => [
119
+ {:key_type => 'HASH', :attribute_name => 'hash_key'},
120
+ ]})
121
+ }
122
+ client
123
+ }
124
+
125
+ expect {
126
+ driver.configure(<<-EOS)
127
+ type dynamodb_alt
128
+ table_name my_table
129
+ timestamp_key timestamp
130
+ expected key EQ val
131
+ EOS
132
+ }.to raise_error("Cannot parse the expected expression (key EQ val): 399: unexpected token at 'val]'")
133
+ end
134
+
135
+ it do
136
+ driver = create_driver
137
+ allow(driver.instance).to receive(:configure_aws)
138
+
139
+ allow(driver.instance).to receive(:create_client) {
140
+ client = double('client')
141
+ allow(client).to receive(:describe_table) {
142
+ Hashie::Mash.new(:table => {
143
+ :key_schema => [
144
+ {:key_type => 'HASH', :attribute_name => 'hash_key'},
145
+ ]})
146
+ }
147
+ client
148
+ }
149
+
150
+ driver.configure(<<-EOS)
151
+ type dynamodb_alt
152
+ table_name my_table
153
+ timestamp_key timestamp
154
+ expected key1 EQ "str",key2 EQ 1
155
+ EOS
156
+
157
+ expected = driver.instance.instance_variable_get(:@expected)
158
+ expect(expected).to eq [["key1", "EQ", "str"], ["key2", "EQ", 1]]
159
+ end
160
+ }
161
+
162
+ context('emit') {
163
+ before(:all) do
164
+ drop_table
165
+ create_table('(id STRING HASH) READ = 20 WRITE = 20')
166
+ end
167
+
168
+ before(:each) do
169
+ truncate_table
170
+ end
171
+
172
+ after(:all) do
173
+ drop_table
174
+ end
175
+
176
+ context('without condition') {
177
+ it do
178
+ run_driver do |d|
179
+ d.emit({'id' => '12345678-1234-1234-1234-123456789001', 'timestamp' => 1409534625001}, time)
180
+ d.emit({'id' => '12345678-1234-1234-1234-123456789002', 'timestamp' => 1409534625002}, time)
181
+ d.emit({'id' => '12345678-1234-1234-1234-123456789003', 'timestamp' => 1409534625003}, time)
182
+ end
183
+
184
+ expect(select_all).to match_array [
185
+ {"id"=>"12345678-1234-1234-1234-123456789001", "timestamp"=>1409534625001},
186
+ {"id"=>"12345678-1234-1234-1234-123456789002", "timestamp"=>1409534625002},
187
+ {"id"=>"12345678-1234-1234-1234-123456789003", "timestamp"=>1409534625003},
188
+ ]
189
+ end
190
+ }
191
+
192
+ context('concurrency > 1') {
193
+ it do
194
+ run_driver(:concurrency => 2) do |d|
195
+ d.emit({'id' => '12345678-1234-1234-1234-123456789001', 'timestamp' => 1409534625001}, time)
196
+ d.emit({'id' => '12345678-1234-1234-1234-123456789002', 'timestamp' => 1409534625002}, time)
197
+ d.emit({'id' => '12345678-1234-1234-1234-123456789003', 'timestamp' => 1409534625003}, time)
198
+ end
199
+
200
+ expect(select_all).to match_array [
201
+ {"id"=>"12345678-1234-1234-1234-123456789001", "timestamp"=>1409534625001},
202
+ {"id"=>"12345678-1234-1234-1234-123456789002", "timestamp"=>1409534625002},
203
+ {"id"=>"12345678-1234-1234-1234-123456789003", "timestamp"=>1409534625003},
204
+ ]
205
+ end
206
+ }
207
+
208
+ context('with condition (1)') {
209
+ it do
210
+ run_driver(:expected => 'id NULL,timestamp LT ${timestamp}', :conditional_operator => 'OR') do |d|
211
+ expect(d.instance.log).not_to receive(:warn)
212
+ d.emit({'id' => '12345678-1234-1234-1234-123456789001', 'timestamp' => 1409534625001}, time)
213
+ d.emit({'id' => '12345678-1234-1234-1234-123456789002', 'timestamp' => 1409534625002}, time)
214
+ d.emit({'id' => '12345678-1234-1234-1234-123456789003', 'timestamp' => 1409534625003}, time)
215
+ end
216
+
217
+ expect(select_all).to match_array [
218
+ {"id"=>"12345678-1234-1234-1234-123456789001", "timestamp"=>1409534625001},
219
+ {"id"=>"12345678-1234-1234-1234-123456789002", "timestamp"=>1409534625002},
220
+ {"id"=>"12345678-1234-1234-1234-123456789003", "timestamp"=>1409534625003},
221
+ ]
222
+
223
+ run_driver(:expected => 'id NULL,timestamp LT ${timestamp}', :conditional_operator => 'OR') do |d|
224
+ expect(d.instance.log).not_to receive(:warn)
225
+ d.emit({'id' => '12345678-1234-1234-1234-123456789001', 'timestamp' => 1409534625004, 'key' => 'val'}, time)
226
+ d.emit({'id' => '12345678-1234-1234-1234-123456789002', 'timestamp' => 1409534625005, 'key' => 'val'}, time)
227
+ d.emit({'id' => '12345678-1234-1234-1234-123456789003', 'timestamp' => 1409534625006, 'key' => 'val'}, time)
228
+ end
229
+
230
+ expect(select_all).to match_array [
231
+ {"id"=>"12345678-1234-1234-1234-123456789001", "timestamp"=>1409534625004, 'key' => 'val'},
232
+ {"id"=>"12345678-1234-1234-1234-123456789002", "timestamp"=>1409534625005, 'key' => 'val'},
233
+ {"id"=>"12345678-1234-1234-1234-123456789003", "timestamp"=>1409534625006, 'key' => 'val'},
234
+ ]
235
+ end
236
+
237
+ it do
238
+ run_driver(:expected => 'id NULL,timestamp LT ${timestamp}', :conditional_operator => 'OR') do |d|
239
+ expect(d.instance.log).not_to receive(:warn)
240
+ d.emit({'id' => '12345678-1234-1234-1234-123456789001', 'timestamp' => 1409534625001}, time)
241
+ d.emit({'id' => '12345678-1234-1234-1234-123456789002', 'timestamp' => 1409534625002}, time)
242
+ d.emit({'id' => '12345678-1234-1234-1234-123456789003', 'timestamp' => 1409534625003}, time)
243
+ end
244
+
245
+ expect(select_all).to match_array [
246
+ {"id"=>"12345678-1234-1234-1234-123456789001", "timestamp"=>1409534625001},
247
+ {"id"=>"12345678-1234-1234-1234-123456789002", "timestamp"=>1409534625002},
248
+ {"id"=>"12345678-1234-1234-1234-123456789003", "timestamp"=>1409534625003},
249
+ ]
250
+
251
+ run_driver(:expected => 'id NULL,timestamp LT ${timestamp}', :conditional_operator => 'OR') do |d|
252
+ expect(d.instance.log).to receive(:warn)
253
+ .with(%!The conditional request failed: {:table_name=>"#{TEST_TABLE_NAME}", :item=>{"id"=>"12345678-1234-1234-1234-123456789001", "timestamp"=>1409534625001, "key"=>"val"}, :expected=>{"id"=>{:comparison_operator=>"NULL"}, "timestamp"=>{:comparison_operator=>"LT", :attribute_value_list=>[1409534625001]}}, :conditional_operator=>"OR"}!)
254
+ expect(d.instance.log).to receive(:warn)
255
+ .with(%!The conditional request failed: {:table_name=>"#{TEST_TABLE_NAME}", :item=>{"id"=>"12345678-1234-1234-1234-123456789002", "timestamp"=>1409534625002, "key"=>"val"}, :expected=>{"id"=>{:comparison_operator=>"NULL"}, "timestamp"=>{:comparison_operator=>"LT", :attribute_value_list=>[1409534625002]}}, :conditional_operator=>"OR"}!)
256
+ expect(d.instance.log).to receive(:warn)
257
+ .with(%!The conditional request failed: {:table_name=>"#{TEST_TABLE_NAME}", :item=>{"id"=>"12345678-1234-1234-1234-123456789003", "timestamp"=>1409534625003, "key"=>"val"}, :expected=>{"id"=>{:comparison_operator=>"NULL"}, "timestamp"=>{:comparison_operator=>"LT", :attribute_value_list=>[1409534625003]}}, :conditional_operator=>"OR"}!)
258
+
259
+ d.emit({'id' => '12345678-1234-1234-1234-123456789001', 'timestamp' => 1409534625001, 'key' => 'val'}, time)
260
+ d.emit({'id' => '12345678-1234-1234-1234-123456789002', 'timestamp' => 1409534625002, 'key' => 'val'}, time)
261
+ d.emit({'id' => '12345678-1234-1234-1234-123456789003', 'timestamp' => 1409534625003, 'key' => 'val'}, time)
262
+ end
263
+
264
+ expect(select_all).to match_array [
265
+ {"id"=>"12345678-1234-1234-1234-123456789001", "timestamp"=>1409534625001},
266
+ {"id"=>"12345678-1234-1234-1234-123456789002", "timestamp"=>1409534625002},
267
+ {"id"=>"12345678-1234-1234-1234-123456789003", "timestamp"=>1409534625003},
268
+ ]
269
+ end
270
+ }
271
+
272
+ context('with condition (2)') {
273
+ it do
274
+ run_driver do |d|
275
+ expect(d.instance.log).not_to receive(:warn)
276
+ d.emit({'id' => '12345678-1234-1234-1234-123456789001', 'timestamp' => 1409534625001, 'key' => 'val'}, time)
277
+ d.emit({'id' => '12345678-1234-1234-1234-123456789002', 'timestamp' => 1409534625002, 'key' => 'val'}, time)
278
+ d.emit({'id' => '12345678-1234-1234-1234-123456789003', 'timestamp' => 1409534625003, 'key' => 'val'}, time)
279
+ end
280
+
281
+ expect(select_all).to match_array [
282
+ {"id"=>"12345678-1234-1234-1234-123456789001", "timestamp"=>1409534625001, 'key' => 'val'},
283
+ {"id"=>"12345678-1234-1234-1234-123456789002", "timestamp"=>1409534625002, 'key' => 'val'},
284
+ {"id"=>"12345678-1234-1234-1234-123456789003", "timestamp"=>1409534625003, 'key' => 'val'},
285
+ ]
286
+
287
+ run_driver(:expected => 'timestamp LE ${timestamp},key EQ "val"') do |d|
288
+ expect(d.instance.log).not_to receive(:warn)
289
+ d.emit({'id' => '12345678-1234-1234-1234-123456789001', 'timestamp' => 1409534625004, 'key' => 'val2'}, time)
290
+ d.emit({'id' => '12345678-1234-1234-1234-123456789002', 'timestamp' => 1409534625005, 'key' => 'val2'}, time)
291
+ d.emit({'id' => '12345678-1234-1234-1234-123456789003', 'timestamp' => 1409534625006, 'key' => 'val2'}, time)
292
+ end
293
+
294
+ expect(select_all).to match_array [
295
+ {"id"=>"12345678-1234-1234-1234-123456789001", "timestamp"=>1409534625004, 'key' => 'val2'},
296
+ {"id"=>"12345678-1234-1234-1234-123456789002", "timestamp"=>1409534625005, 'key' => 'val2'},
297
+ {"id"=>"12345678-1234-1234-1234-123456789003", "timestamp"=>1409534625006, 'key' => 'val2'},
298
+ ]
299
+ end
300
+
301
+ it do
302
+ run_driver do |d|
303
+ expect(d.instance.log).not_to receive(:warn)
304
+ d.emit({'id' => '12345678-1234-1234-1234-123456789001', 'timestamp' => 1409534625001, 'key' => 'val3'}, time)
305
+ d.emit({'id' => '12345678-1234-1234-1234-123456789002', 'timestamp' => 1409534625002, 'key' => 'val3'}, time)
306
+ d.emit({'id' => '12345678-1234-1234-1234-123456789003', 'timestamp' => 1409534625003, 'key' => 'val3'}, time)
307
+ end
308
+
309
+ expect(select_all).to match_array [
310
+ {"id"=>"12345678-1234-1234-1234-123456789001", "timestamp"=>1409534625001, 'key' => 'val3'},
311
+ {"id"=>"12345678-1234-1234-1234-123456789002", "timestamp"=>1409534625002, 'key' => 'val3'},
312
+ {"id"=>"12345678-1234-1234-1234-123456789003", "timestamp"=>1409534625003, 'key' => 'val3'},
313
+ ]
314
+
315
+ run_driver(:expected => 'timestamp LE ${timestamp},key EQ "val"') do |d|
316
+ expect(d.instance.log).to receive(:warn)
317
+ .with(%!The conditional request failed: {:table_name=>"#{TEST_TABLE_NAME}", :item=>{"id"=>"12345678-1234-1234-1234-123456789001", "timestamp"=>1409534625004, "key"=>"val2"}, :expected=>{"timestamp"=>{:comparison_operator=>"LE", :attribute_value_list=>[1409534625004]}, "key"=>{:comparison_operator=>"EQ", :attribute_value_list=>["val"]}}, :conditional_operator=>"AND"}!)
318
+ expect(d.instance.log).to receive(:warn)
319
+ .with(%!The conditional request failed: {:table_name=>"#{TEST_TABLE_NAME}", :item=>{"id"=>"12345678-1234-1234-1234-123456789002", "timestamp"=>1409534625005, "key"=>"val2"}, :expected=>{"timestamp"=>{:comparison_operator=>"LE", :attribute_value_list=>[1409534625005]}, "key"=>{:comparison_operator=>"EQ", :attribute_value_list=>["val"]}}, :conditional_operator=>"AND"}!)
320
+ expect(d.instance.log).to receive(:warn)
321
+ .with(%!The conditional request failed: {:table_name=>"#{TEST_TABLE_NAME}", :item=>{"id"=>"12345678-1234-1234-1234-123456789003", "timestamp"=>1409534625006, "key"=>"val2"}, :expected=>{"timestamp"=>{:comparison_operator=>"LE", :attribute_value_list=>[1409534625006]}, "key"=>{:comparison_operator=>"EQ", :attribute_value_list=>["val"]}}, :conditional_operator=>"AND"}!)
322
+
323
+ d.emit({'id' => '12345678-1234-1234-1234-123456789001', 'timestamp' => 1409534625004, 'key' => 'val2'}, time)
324
+ d.emit({'id' => '12345678-1234-1234-1234-123456789002', 'timestamp' => 1409534625005, 'key' => 'val2'}, time)
325
+ d.emit({'id' => '12345678-1234-1234-1234-123456789003', 'timestamp' => 1409534625006, 'key' => 'val2'}, time)
326
+ end
327
+
328
+ expect(select_all).to match_array [
329
+ {"id"=>"12345678-1234-1234-1234-123456789001", "timestamp"=>1409534625001, 'key' => 'val3'},
330
+ {"id"=>"12345678-1234-1234-1234-123456789002", "timestamp"=>1409534625002, 'key' => 'val3'},
331
+ {"id"=>"12345678-1234-1234-1234-123456789003", "timestamp"=>1409534625003, 'key' => 'val3'},
332
+ ]
333
+ end
334
+
335
+ it do
336
+ run_driver do |d|
337
+ expect(d.instance.log).not_to receive(:warn)
338
+ d.emit({'id' => '12345678-1234-1234-1234-123456789001', 'timestamp' => 1409534625001, 'key' => 'val3'}, time)
339
+ d.emit({'id' => '12345678-1234-1234-1234-123456789002', 'timestamp' => 1409534625002, 'key' => 'val3'}, time)
340
+ d.emit({'id' => '12345678-1234-1234-1234-123456789003', 'timestamp' => 1409534625003, 'key' => 'val3'}, time)
341
+ end
342
+
343
+ expect(select_all).to match_array [
344
+ {"id"=>"12345678-1234-1234-1234-123456789001", "timestamp"=>1409534625001, 'key' => 'val3'},
345
+ {"id"=>"12345678-1234-1234-1234-123456789002", "timestamp"=>1409534625002, 'key' => 'val3'},
346
+ {"id"=>"12345678-1234-1234-1234-123456789003", "timestamp"=>1409534625003, 'key' => 'val3'},
347
+ ]
348
+
349
+ run_driver(:expected => 'timestamp LE ${timestamp},key EQ "val"', :conditional_operator => 'OR') do |d|
350
+ expect(d.instance.log).to_not receive(:warn)
351
+ d.emit({'id' => '12345678-1234-1234-1234-123456789001', 'timestamp' => 1409534625004, 'key' => 'val2'}, time)
352
+ d.emit({'id' => '12345678-1234-1234-1234-123456789002', 'timestamp' => 1409534625005, 'key' => 'val2'}, time)
353
+ d.emit({'id' => '12345678-1234-1234-1234-123456789003', 'timestamp' => 1409534625006, 'key' => 'val2'}, time)
354
+ end
355
+
356
+ expect(select_all).to match_array [
357
+ {"id"=>"12345678-1234-1234-1234-123456789001", "timestamp"=>1409534625004, 'key' => 'val2'},
358
+ {"id"=>"12345678-1234-1234-1234-123456789002", "timestamp"=>1409534625005, 'key' => 'val2'},
359
+ {"id"=>"12345678-1234-1234-1234-123456789003", "timestamp"=>1409534625006, 'key' => 'val2'},
360
+ ]
361
+ end
362
+ }
363
+
364
+ context('key dose not exist') {
365
+ it do
366
+ run_driver do |d|
367
+ expect(d.instance.log).to receive(:warn)
368
+ .with(%!Hash Key 'id' does not exist in the record: {"timestamp"=>1409534625001, "key"=>"val"}!)
369
+ d.emit({'timestamp' => 1409534625001, 'key' => 'val'}, time)
370
+ end
371
+
372
+ expect(select_all).to match_array []
373
+ end
374
+
375
+ it do
376
+ run_driver do |d|
377
+ expect(d.instance.log).to receive(:warn)
378
+ .with(%!Timestamp Key 'timestamp' does not exist in the record: {"id"=>"12345678-1234-1234-1234-123456789001", "key"=>"val"}!)
379
+ d.emit({'id' => '12345678-1234-1234-1234-123456789001', 'key' => 'val'}, time)
380
+ end
381
+
382
+ expect(select_all).to match_array []
383
+ end
384
+ }
385
+
386
+ context('aggregate') {
387
+ it do
388
+ run_driver(:flush_interval => 3) do |d|
389
+ d.emit({'id' => '12345678-1234-1234-1234-123456789001', 'timestamp' => 1409534625003}, time)
390
+ d.emit({'id' => '12345678-1234-1234-1234-123456789001', 'timestamp' => 1409534625002}, time)
391
+ d.emit({'id' => '12345678-1234-1234-1234-123456789001', 'timestamp' => 1409534625001}, time)
392
+
393
+ d.emit({'id' => '12345678-1234-1234-1234-123456789002', 'timestamp' => 1409534625003}, time)
394
+ d.emit({'id' => '12345678-1234-1234-1234-123456789002', 'timestamp' => 1409534625002}, time)
395
+ d.emit({'id' => '12345678-1234-1234-1234-123456789002', 'timestamp' => 1409534625001}, time)
396
+
397
+ d.emit({'id' => '12345678-1234-1234-1234-123456789003', 'timestamp' => 1409534625003}, time)
398
+ d.emit({'id' => '12345678-1234-1234-1234-123456789003', 'timestamp' => 1409534625002}, time)
399
+ d.emit({'id' => '12345678-1234-1234-1234-123456789003', 'timestamp' => 1409534625001}, time)
400
+ end
401
+
402
+ expect(select_all).to match_array [
403
+ {"id"=>"12345678-1234-1234-1234-123456789001", "timestamp"=>1409534625003},
404
+ {"id"=>"12345678-1234-1234-1234-123456789002", "timestamp"=>1409534625003},
405
+ {"id"=>"12345678-1234-1234-1234-123456789003", "timestamp"=>1409534625003},
406
+ ]
407
+ end
408
+
409
+ it do
410
+ run_driver(:flush_interval => 3) do |d|
411
+ expect(d.instance).to receive(:put_record).with('id' => '12345678-1234-1234-1234-123456789001', 'timestamp' => 1409534625003)
412
+ expect(d.instance).to receive(:put_record).with('id' => '12345678-1234-1234-1234-123456789002', 'timestamp' => 1409534625003)
413
+ expect(d.instance).to receive(:put_record).with('id' => '12345678-1234-1234-1234-123456789003', 'timestamp' => 1409534625003)
414
+
415
+ d.emit({'id' => '12345678-1234-1234-1234-123456789001', 'timestamp' => 1409534625003}, time)
416
+ d.emit({'id' => '12345678-1234-1234-1234-123456789001', 'timestamp' => 1409534625002}, time)
417
+ d.emit({'id' => '12345678-1234-1234-1234-123456789001', 'timestamp' => 1409534625001}, time)
418
+
419
+ d.emit({'id' => '12345678-1234-1234-1234-123456789002', 'timestamp' => 1409534625003}, time)
420
+ d.emit({'id' => '12345678-1234-1234-1234-123456789002', 'timestamp' => 1409534625002}, time)
421
+ d.emit({'id' => '12345678-1234-1234-1234-123456789002', 'timestamp' => 1409534625001}, time)
422
+
423
+ d.emit({'id' => '12345678-1234-1234-1234-123456789003', 'timestamp' => 1409534625003}, time)
424
+ d.emit({'id' => '12345678-1234-1234-1234-123456789003', 'timestamp' => 1409534625002}, time)
425
+ d.emit({'id' => '12345678-1234-1234-1234-123456789003', 'timestamp' => 1409534625001}, time)
426
+ end
427
+ end
428
+ }
429
+ }
430
+ end
@@ -0,0 +1,99 @@
1
+ require 'fluent/test'
2
+ require 'fluent/plugin/out_dynamodb_alt'
3
+ require 'hashie'
4
+ require 'securerandom'
5
+
6
+ DRIVER_DEFAULT_TAG = 'test.default'
7
+ TEST_TABLE_NAME = 'my_table-' + SecureRandom.uuid
8
+ TEST_AWS_KEY_ID = ENV['OUT_DYNAMODB_ALT_SPEC_AWS_KEY_ID'] || 'scott'
9
+ TEST_AWS_SEC_KEY = ENV['OUT_DYNAMODB_ALT_SPEC_AWS_SEC_KEY'] || 'tiger'
10
+ TEST_REGION = ENV['OUT_DYNAMODB_ALT_SPEC_REGION'] || 'us-west-1'
11
+
12
+ # Disable Test::Unit
13
+ module Test::Unit::RunCount; def run(*); end; end
14
+
15
+ RSpec.configure do |config|
16
+ config.before(:all) do
17
+ Fluent::Test.setup
18
+ end
19
+ end
20
+
21
+ def query(sql)
22
+ out = `ddbcli -k '#{TEST_AWS_KEY_ID}' -s '#{TEST_AWS_SEC_KEY}' -r #{TEST_REGION} -e "#{sql.gsub(/\n/, ' ')}"`
23
+ raise out unless $?.success?
24
+ return out
25
+ end
26
+
27
+ def select_all
28
+ JSON.parse(query("SELECT ALL * FROM #{TEST_TABLE_NAME}"))
29
+ end
30
+
31
+ def create_driver(tag = DRIVER_DEFAULT_TAG)
32
+ Fluent::Test::OutputTestDriver.new(Fluent::DynamodbAltOutput, tag)
33
+ end
34
+
35
+ def run_driver(options = {})
36
+ options = {
37
+ :flush_interval => 0
38
+ }.merge(options.dup)
39
+
40
+ additional_options = options.map {|key, value|
41
+ "#{key} #{value}"
42
+ }.join("\n")
43
+
44
+ fluentd_conf = <<-EOS
45
+ type dynamodb_alt
46
+ aws_key_id #{TEST_AWS_KEY_ID}
47
+ aws_sec_key #{TEST_AWS_SEC_KEY}
48
+ region #{TEST_REGION}
49
+ table_name #{TEST_TABLE_NAME}
50
+ timestamp_key timestamp
51
+ #{additional_options}
52
+ EOS
53
+
54
+ tag = options[:tag] || DRIVER_DEFAULT_TAG
55
+ driver = create_driver(tag)
56
+
57
+ driver.configure(fluentd_conf)
58
+
59
+ driver.run do
60
+ yield(driver)
61
+ end
62
+ end
63
+
64
+ def create_table(attrs)
65
+ query("CREATE TABLE #{TEST_TABLE_NAME} #{attrs}")
66
+
67
+ loop do
68
+ status = query("SHOW TABLE STATUS LIKE '#{TEST_TABLE_NAME}'")
69
+ status = JSON.parse(status)
70
+ break if (not status[TEST_TABLE_NAME] or status[TEST_TABLE_NAME]['TableStatus'] == 'ACTIVE')
71
+ end
72
+ end
73
+
74
+ def drop_table
75
+ table_is_exist = proc do
76
+ tables = query("SHOW TABLES LIKE '#{TEST_TABLE_NAME}'")
77
+ tables = JSON.parse(tables)
78
+ not tables.empty?
79
+ end
80
+
81
+ return unless table_is_exist.call
82
+
83
+ query("DROP TABLE #{TEST_TABLE_NAME}")
84
+
85
+ loop do
86
+ tables = query("SHOW TABLES LIKE '#{TEST_TABLE_NAME}'")
87
+ tables = JSON.parse(tables)
88
+ break unless table_is_exist.call
89
+ end
90
+ end
91
+
92
+ def truncate_table
93
+ query("DELETE ALL FROM #{TEST_TABLE_NAME}")
94
+
95
+ loop do
96
+ count = query("SELECT ALL COUNT(*) FROM #{TEST_TABLE_NAME}").strip
97
+ break if count == '0'
98
+ end
99
+ end
metadata ADDED
@@ -0,0 +1,169 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fluent-plugin-dynamodb-alt
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Genki Sugawara
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-09-04 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: fluentd
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: aws-sdk-core
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: 2.0.0.rc15
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: 2.0.0.rc15
41
+ - !ruby/object:Gem::Dependency
42
+ name: parallel
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - '>='
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ~>
74
+ - !ruby/object:Gem::Version
75
+ version: '10.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ~>
81
+ - !ruby/object:Gem::Version
82
+ version: '10.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - '>='
88
+ - !ruby/object:Gem::Version
89
+ version: 3.0.0
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - '>='
95
+ - !ruby/object:Gem::Version
96
+ version: 3.0.0
97
+ - !ruby/object:Gem::Dependency
98
+ name: hashie
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - '>='
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - '>='
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: ddbcli
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - '>='
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ description: Fluent plugin to output to DynamoDB.
126
+ email:
127
+ - sgwr_dts@yahoo.co.jp
128
+ executables: []
129
+ extensions: []
130
+ extra_rdoc_files: []
131
+ files:
132
+ - .gitignore
133
+ - .rspec
134
+ - .travis.yml
135
+ - Gemfile
136
+ - LICENSE.txt
137
+ - README.md
138
+ - Rakefile
139
+ - fluent-plugin-dynamodb-alt.gemspec
140
+ - lib/fluent/plugin/out_dynamodb_alt.rb
141
+ - spec/out_dynamodb_alt_spec.rb
142
+ - spec/spec_helper.rb
143
+ homepage: https://github.com/winebarrel/fluent-plugin-dynamodb-alt
144
+ licenses:
145
+ - MIT
146
+ metadata: {}
147
+ post_install_message:
148
+ rdoc_options: []
149
+ require_paths:
150
+ - lib
151
+ required_ruby_version: !ruby/object:Gem::Requirement
152
+ requirements:
153
+ - - '>='
154
+ - !ruby/object:Gem::Version
155
+ version: '0'
156
+ required_rubygems_version: !ruby/object:Gem::Requirement
157
+ requirements:
158
+ - - '>='
159
+ - !ruby/object:Gem::Version
160
+ version: '0'
161
+ requirements: []
162
+ rubyforge_project:
163
+ rubygems_version: 2.0.14
164
+ signing_key:
165
+ specification_version: 4
166
+ summary: Fluent plugin to output to DynamoDB.
167
+ test_files:
168
+ - spec/out_dynamodb_alt_spec.rb
169
+ - spec/spec_helper.rb