fluent-plugin-redshift-out 0.3.0 → 0.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/VERSION +1 -1
- data/fluent-plugin-redshift-out.gemspec +2 -2
- data/lib/fluent/plugin/out_redshift-out.rb +159 -65
- data/test/plugin/test_out_redshift.rb +85 -114
- metadata +33 -45
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 5b20d3794db1fa89bcd3023c16a7521da561463d
|
4
|
+
data.tar.gz: c07149ac3e5cf6978b7ccd6b72becb7407274d88
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: fea913edcc8f21adf6e7c9b9068b9ae0830fc2813fe0b37640b868dcff1dbdb45fb55566f8a1e9c2e6acf59dfb3a1511105181941523811238aa98a36ddbc56b
|
7
|
+
data.tar.gz: 775d3b77816faa0c010e3f3eef54cec753f2ed12704bc1c89ed890c2db7464d2b206ff25a40e20ce9b63ca82e193fe10691e229c0c86e4ef987c9c33090c8a41
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.4.0
|
@@ -16,8 +16,8 @@ Gem::Specification.new do |gem|
|
|
16
16
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
17
17
|
gem.require_paths = ["lib"]
|
18
18
|
|
19
|
-
gem.add_dependency "fluentd", "
|
20
|
-
gem.add_dependency "aws-sdk", "
|
19
|
+
gem.add_dependency "fluentd", [">= 0.10.0", "< 2"]
|
20
|
+
gem.add_dependency "aws-sdk-v1", ">= 1.6.3"
|
21
21
|
gem.add_dependency "multi_json", "~> 1.10"
|
22
22
|
gem.add_dependency "yajl-ruby", "~> 1.2"
|
23
23
|
gem.add_dependency "pg", "~> 0.17.0"
|
@@ -4,12 +4,14 @@ module Fluent
|
|
4
4
|
class RedshiftOutput < BufferedOutput
|
5
5
|
Fluent::Plugin.register_output('redshift-out', self)
|
6
6
|
|
7
|
+
NULL_CHAR_FOR_COPY = "\\N"
|
8
|
+
|
7
9
|
# ignore load table error. (invalid data format)
|
8
10
|
IGNORE_REDSHIFT_ERROR_REGEXP = /^ERROR: Load into table '[^']+' failed\./
|
9
11
|
|
10
12
|
def initialize
|
11
13
|
super
|
12
|
-
require 'aws-sdk'
|
14
|
+
require 'aws-sdk-v1'
|
13
15
|
require 'zlib'
|
14
16
|
require 'time'
|
15
17
|
require 'tempfile'
|
@@ -22,8 +24,8 @@ class RedshiftOutput < BufferedOutput
|
|
22
24
|
|
23
25
|
config_param :record_log_tag, :string, :default => 'log'
|
24
26
|
# s3
|
25
|
-
config_param :aws_key_id, :string, :default => nil
|
26
|
-
config_param :aws_sec_key, :string, :default => nil
|
27
|
+
config_param :aws_key_id, :string, :secret => true, :default => nil
|
28
|
+
config_param :aws_sec_key, :string, :secret => true, :default => nil
|
27
29
|
config_param :s3_bucket, :string
|
28
30
|
config_param :s3_region, :string, :default => nil
|
29
31
|
config_param :path, :string, :default => ""
|
@@ -34,7 +36,7 @@ class RedshiftOutput < BufferedOutput
|
|
34
36
|
config_param :redshift_port, :integer, :default => 5439
|
35
37
|
config_param :redshift_dbname, :string
|
36
38
|
config_param :redshift_user, :string
|
37
|
-
config_param :redshift_password, :string
|
39
|
+
config_param :redshift_password, :string, :secret => true
|
38
40
|
config_param :redshift_tablename, :string
|
39
41
|
config_param :redshift_schemaname, :string, :default => nil
|
40
42
|
config_param :redshift_copy_base_options, :string , :default => "FILLRECORD ACCEPTANYDATE TRUNCATECOLUMNS"
|
@@ -43,6 +45,8 @@ class RedshiftOutput < BufferedOutput
|
|
43
45
|
# file format
|
44
46
|
config_param :file_type, :string, :default => nil # json, tsv, csv, msgpack
|
45
47
|
config_param :delimiter, :string, :default => nil
|
48
|
+
# maintenance
|
49
|
+
config_param :maintenance_file_path, :string, :default => nil
|
46
50
|
# for debug
|
47
51
|
config_param :log_suffix, :string, :default => ''
|
48
52
|
|
@@ -61,6 +65,8 @@ class RedshiftOutput < BufferedOutput
|
|
61
65
|
}
|
62
66
|
@delimiter = determine_delimiter(@file_type) if @delimiter.nil? or @delimiter.empty?
|
63
67
|
$log.debug format_log("redshift file_type:#{@file_type} delimiter:'#{@delimiter}'")
|
68
|
+
@table_name_with_schema = [@redshift_schemaname, @redshift_tablename].compact.join('.')
|
69
|
+
@maintenance_monitor = MaintenanceMonitor.new(@maintenance_file_path)
|
64
70
|
end
|
65
71
|
|
66
72
|
def start
|
@@ -74,6 +80,7 @@ class RedshiftOutput < BufferedOutput
|
|
74
80
|
options[:region] = @s3_region if @s3_region
|
75
81
|
@s3 = AWS::S3.new(options)
|
76
82
|
@bucket = @s3.buckets[@s3_bucket]
|
83
|
+
@redshift_connection = RedshiftConnection.new(@db_conf)
|
77
84
|
end
|
78
85
|
|
79
86
|
def format(tag, time, record)
|
@@ -88,6 +95,7 @@ class RedshiftOutput < BufferedOutput
|
|
88
95
|
|
89
96
|
def write(chunk)
|
90
97
|
$log.debug format_log("start creating gz.")
|
98
|
+
@maintenance_monitor.check_maintenance!
|
91
99
|
|
92
100
|
# create a gz file
|
93
101
|
tmp = Tempfile.new("s3-")
|
@@ -117,7 +125,7 @@ class RedshiftOutput < BufferedOutput
|
|
117
125
|
# copy gz on s3 to redshift
|
118
126
|
s3_uri = "s3://#{@s3_bucket}/#{s3path}"
|
119
127
|
credentials = @s3.client.credential_provider.credentials
|
120
|
-
sql = "copy #{table_name_with_schema} from '#{s3_uri}'"
|
128
|
+
sql = "copy #{@table_name_with_schema} from '#{s3_uri}'"
|
121
129
|
sql += " CREDENTIALS 'aws_access_key_id=#{credentials[:access_key_id]};aws_secret_access_key=#{credentials[:secret_access_key]}"
|
122
130
|
sql += ";token=#{credentials[:session_token]}" if credentials[:session_token]
|
123
131
|
sql += "' delimiter '#{@delimiter}' GZIP ESCAPE #{@redshift_copy_base_options} #{@redshift_copy_options};"
|
@@ -125,25 +133,26 @@ class RedshiftOutput < BufferedOutput
|
|
125
133
|
$log.debug format_log("start copying. s3_uri=#{s3_uri}")
|
126
134
|
|
127
135
|
begin
|
128
|
-
|
129
|
-
conn.exec(sql)
|
136
|
+
@redshift_connection.exec(sql)
|
130
137
|
$log.info format_log("completed copying to redshift. s3_uri=#{s3_uri}")
|
131
|
-
rescue
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
138
|
+
rescue RedshiftError => e
|
139
|
+
if e.to_s =~ IGNORE_REDSHIFT_ERROR_REGEXP
|
140
|
+
$log.error format_log("failed to copy data into redshift due to load error. s3_uri=#{s3_uri}"), :error=>e.to_s
|
141
|
+
return false # for debug
|
142
|
+
end
|
143
|
+
raise e
|
137
144
|
end
|
138
145
|
true # for debug
|
139
146
|
end
|
140
147
|
|
141
148
|
protected
|
149
|
+
|
142
150
|
def format_log(message)
|
143
151
|
(@log_suffix and not @log_suffix.empty?) ? "#{message} #{@log_suffix}" : message
|
144
152
|
end
|
145
153
|
|
146
154
|
private
|
155
|
+
|
147
156
|
def json?
|
148
157
|
@file_type == 'json'
|
149
158
|
end
|
@@ -165,11 +174,11 @@ class RedshiftOutput < BufferedOutput
|
|
165
174
|
|
166
175
|
def create_gz_file_from_structured_data(dst_file, chunk, delimiter)
|
167
176
|
# fetch the table definition from redshift
|
168
|
-
redshift_table_columns = fetch_table_columns
|
177
|
+
redshift_table_columns = @redshift_connection.fetch_table_columns(@redshift_tablename, @redshift_schemaname)
|
169
178
|
if redshift_table_columns == nil
|
170
179
|
raise "failed to fetch the redshift table definition."
|
171
180
|
elsif redshift_table_columns.empty?
|
172
|
-
$log.warn format_log("no table on redshift. table_name=#{table_name_with_schema}")
|
181
|
+
$log.warn format_log("no table on redshift. table_name=#{@table_name_with_schema}")
|
173
182
|
return nil
|
174
183
|
end
|
175
184
|
|
@@ -184,12 +193,7 @@ class RedshiftOutput < BufferedOutput
|
|
184
193
|
tsv_text = hash_to_table_text(redshift_table_columns, hash, delimiter)
|
185
194
|
gzw.write(tsv_text) if tsv_text and not tsv_text.empty?
|
186
195
|
rescue => e
|
187
|
-
|
188
|
-
$log.error format_log("failed to create table text from json. text=(#{record[@record_log_tag]})"), :error=>e.to_s
|
189
|
-
else
|
190
|
-
$log.error format_log("failed to create table text from msgpack. text=(#{record[@record_log_tag]})"), :error=>e.to_s
|
191
|
-
end
|
192
|
-
|
196
|
+
$log.error format_log("failed to create table text from #{@file_type}. text=(#{record[@record_log_tag]})"), :error=>e.to_s
|
193
197
|
$log.error_backtrace
|
194
198
|
end
|
195
199
|
end
|
@@ -211,27 +215,6 @@ class RedshiftOutput < BufferedOutput
|
|
211
215
|
end
|
212
216
|
end
|
213
217
|
|
214
|
-
def fetch_table_columns
|
215
|
-
begin
|
216
|
-
columns = nil
|
217
|
-
conn = PG.connect(@db_conf)
|
218
|
-
conn.exec(fetch_columns_sql_with_schema) do |result|
|
219
|
-
columns = result.collect{|row| row['column_name']}
|
220
|
-
end
|
221
|
-
columns
|
222
|
-
ensure
|
223
|
-
conn.close rescue nil if conn
|
224
|
-
end
|
225
|
-
end
|
226
|
-
|
227
|
-
def fetch_columns_sql_with_schema
|
228
|
-
@fetch_columns_sql ||= if @redshift_schemaname
|
229
|
-
"select column_name from INFORMATION_SCHEMA.COLUMNS where table_schema = '#{@redshift_schemaname}' and table_name = '#{@redshift_tablename}' order by ordinal_position;"
|
230
|
-
else
|
231
|
-
"select column_name from INFORMATION_SCHEMA.COLUMNS where table_name = '#{@redshift_tablename}' order by ordinal_position;"
|
232
|
-
end
|
233
|
-
end
|
234
|
-
|
235
218
|
def json_to_hash(json_text)
|
236
219
|
return nil if json_text.to_s.empty?
|
237
220
|
|
@@ -245,18 +228,9 @@ class RedshiftOutput < BufferedOutput
|
|
245
228
|
return "" unless hash
|
246
229
|
|
247
230
|
# extract values from hash
|
248
|
-
val_list = redshift_table_columns.collect
|
249
|
-
val = hash[cn]
|
250
|
-
val = MultiJson.dump(val) if val.kind_of?(Hash) or val.kind_of?(Array)
|
251
|
-
|
252
|
-
if val.to_s.empty?
|
253
|
-
nil
|
254
|
-
else
|
255
|
-
val.to_s
|
256
|
-
end
|
257
|
-
end
|
231
|
+
val_list = redshift_table_columns.collect {|cn| hash[cn]}
|
258
232
|
|
259
|
-
if val_list.all?{|v| v.nil?
|
233
|
+
if val_list.all?{|v| v.nil?}
|
260
234
|
$log.warn format_log("no data match for table columns on redshift. data=#{hash} table_columns=#{redshift_table_columns}")
|
261
235
|
return ""
|
262
236
|
end
|
@@ -265,14 +239,22 @@ class RedshiftOutput < BufferedOutput
|
|
265
239
|
end
|
266
240
|
|
267
241
|
def generate_line_with_delimiter(val_list, delimiter)
|
268
|
-
val_list
|
269
|
-
|
270
|
-
|
242
|
+
val_list.collect do |val|
|
243
|
+
case val
|
244
|
+
when nil
|
245
|
+
NULL_CHAR_FOR_COPY
|
246
|
+
when ''
|
247
|
+
''
|
248
|
+
when Hash, Array
|
249
|
+
escape_text_for_copy(MultiJson.dump(val))
|
271
250
|
else
|
272
|
-
val.
|
251
|
+
escape_text_for_copy(val.to_s)
|
273
252
|
end
|
274
|
-
end
|
275
|
-
|
253
|
+
end.join(delimiter) + "\n"
|
254
|
+
end
|
255
|
+
|
256
|
+
def escape_text_for_copy(val)
|
257
|
+
val.gsub(/\\|\t|\n/, {"\\" => "\\\\", "\t" => "\\\t", "\n" => "\\\n"}) # escape tab, newline and backslash
|
276
258
|
end
|
277
259
|
|
278
260
|
def create_s3path(bucket, path)
|
@@ -286,12 +268,124 @@ class RedshiftOutput < BufferedOutput
|
|
286
268
|
s3path
|
287
269
|
end
|
288
270
|
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
271
|
+
class RedshiftError < StandardError
|
272
|
+
def initialize(msg)
|
273
|
+
case msg
|
274
|
+
when PG::Error
|
275
|
+
@pg_error = msg
|
276
|
+
super(msg.to_s)
|
277
|
+
set_backtrace(msg.backtrace)
|
278
|
+
else
|
279
|
+
super
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
attr_accessor :pg_error
|
284
|
+
end
|
285
|
+
|
286
|
+
class RedshiftConnection
|
287
|
+
REDSHIFT_CONNECT_TIMEOUT = 10.0 # 10sec
|
288
|
+
|
289
|
+
def initialize(db_conf)
|
290
|
+
@db_conf = db_conf
|
291
|
+
@connection = nil
|
292
|
+
end
|
293
|
+
|
294
|
+
attr_reader :db_conf
|
295
|
+
|
296
|
+
def fetch_table_columns(table_name, schema_name)
|
297
|
+
columns = nil
|
298
|
+
exec(fetch_columns_sql(table_name, schema_name)) do |result|
|
299
|
+
columns = result.collect{|row| row['column_name']}
|
300
|
+
end
|
301
|
+
columns
|
302
|
+
end
|
303
|
+
|
304
|
+
def exec(sql, &block)
|
305
|
+
conn = @connection
|
306
|
+
conn = create_redshift_connection if conn.nil?
|
307
|
+
if block
|
308
|
+
conn.exec(sql) {|result| block.call(result)}
|
309
|
+
else
|
310
|
+
conn.exec(sql)
|
311
|
+
end
|
312
|
+
rescue PG::Error => e
|
313
|
+
raise RedshiftError.new(e)
|
314
|
+
ensure
|
315
|
+
conn.close if conn && @connection.nil?
|
316
|
+
end
|
317
|
+
|
318
|
+
def connect_start
|
319
|
+
@connection = create_redshift_connection
|
320
|
+
end
|
321
|
+
|
322
|
+
def close
|
323
|
+
@connection.close rescue nil if @connection
|
324
|
+
@connection = nil
|
325
|
+
end
|
326
|
+
|
327
|
+
private
|
328
|
+
|
329
|
+
def create_redshift_connection
|
330
|
+
hostaddr = IPSocket.getaddress(db_conf[:host])
|
331
|
+
db_conf[:hostaddr] = hostaddr
|
332
|
+
|
333
|
+
conn = PG::Connection.connect_start(db_conf)
|
334
|
+
raise RedshiftError.new("Unable to create a new connection.") unless conn
|
335
|
+
if conn.status == PG::CONNECTION_BAD
|
336
|
+
raise RedshiftError.new("Connection failed: %s" % [ conn.error_message ])
|
337
|
+
end
|
338
|
+
|
339
|
+
socket = conn.socket_io
|
340
|
+
poll_status = PG::PGRES_POLLING_WRITING
|
341
|
+
until poll_status == PG::PGRES_POLLING_OK || poll_status == PG::PGRES_POLLING_FAILED
|
342
|
+
case poll_status
|
343
|
+
when PG::PGRES_POLLING_READING
|
344
|
+
IO.select([socket], nil, nil, REDSHIFT_CONNECT_TIMEOUT) or
|
345
|
+
raise RedshiftError.new("Asynchronous connection timed out!(READING)")
|
346
|
+
when PG::PGRES_POLLING_WRITING
|
347
|
+
IO.select(nil, [socket], nil, REDSHIFT_CONNECT_TIMEOUT) or
|
348
|
+
raise RedshiftError.new("Asynchronous connection timed out!(WRITING)")
|
349
|
+
end
|
350
|
+
poll_status = conn.connect_poll
|
351
|
+
end
|
352
|
+
|
353
|
+
unless conn.status == PG::CONNECTION_OK
|
354
|
+
raise RedshiftError, ("Connect failed: %s" % [conn.error_message.to_s.lines.uniq.join(" ")])
|
355
|
+
end
|
356
|
+
|
357
|
+
conn
|
358
|
+
rescue => e
|
359
|
+
conn.close rescue nil if conn
|
360
|
+
raise RedshiftError.new(e) if e.kind_of?(PG::Error)
|
361
|
+
raise e
|
362
|
+
end
|
363
|
+
|
364
|
+
def fetch_columns_sql(table_name, schema_name = nil)
|
365
|
+
sql = "select column_name from INFORMATION_SCHEMA.COLUMNS where table_name = '#{table_name}'"
|
366
|
+
sql << " and table_schema = '#{schema_name}'" if schema_name
|
367
|
+
sql << " order by ordinal_position;"
|
368
|
+
sql
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
372
|
+
class MaintenanceError < StandardError
|
373
|
+
end
|
374
|
+
|
375
|
+
class MaintenanceMonitor
|
376
|
+
def initialize(maintenance_file_path)
|
377
|
+
@file_path = maintenance_file_path
|
378
|
+
end
|
379
|
+
|
380
|
+
def in_maintenance?
|
381
|
+
!!(@file_path && File.exists?(@file_path))
|
382
|
+
end
|
383
|
+
|
384
|
+
def check_maintenance!
|
385
|
+
if in_maintenance?
|
386
|
+
raise MaintenanceError.new("Service is in maintenance mode - maintenance_file_path:#{@file_path}")
|
387
|
+
end
|
388
|
+
end
|
295
389
|
end
|
296
390
|
end
|
297
391
|
|
@@ -1,19 +1,22 @@
|
|
1
1
|
require 'test_helper'
|
2
2
|
|
3
3
|
require 'fluent/test'
|
4
|
-
require 'fluent/plugin/out_redshift'
|
4
|
+
require 'fluent/plugin/out_redshift-out'
|
5
5
|
require 'flexmock/test_unit'
|
6
6
|
require 'zlib'
|
7
7
|
|
8
8
|
|
9
9
|
class RedshiftOutputTest < Test::Unit::TestCase
|
10
10
|
def setup
|
11
|
-
require 'aws-sdk'
|
11
|
+
require 'aws-sdk-v1'
|
12
12
|
require 'pg'
|
13
13
|
require 'csv'
|
14
14
|
Fluent::Test.setup
|
15
|
+
PG::Error.module_eval { attr_accessor :result}
|
15
16
|
end
|
16
17
|
|
18
|
+
MAINTENANCE_FILE_PATH_FOR_TEST = "/tmp/fluentd_redshift_plugin_test_maintenance"
|
19
|
+
|
17
20
|
CONFIG_BASE= %[
|
18
21
|
aws_key_id test_key_id
|
19
22
|
aws_sec_key test_sec_key
|
@@ -27,6 +30,7 @@ class RedshiftOutputTest < Test::Unit::TestCase
|
|
27
30
|
buffer_type memory
|
28
31
|
utc
|
29
32
|
log_suffix id:5 host:localhost
|
33
|
+
maintenance_file_path #{MAINTENANCE_FILE_PATH_FOR_TEST}
|
30
34
|
]
|
31
35
|
CONFIG_CSV= %[
|
32
36
|
#{CONFIG_BASE}
|
@@ -106,6 +110,7 @@ class RedshiftOutputTest < Test::Unit::TestCase
|
|
106
110
|
assert_equal "csv", d.instance.file_type
|
107
111
|
assert_equal ",", d.instance.delimiter
|
108
112
|
assert_equal true, d.instance.utc
|
113
|
+
assert_equal MAINTENANCE_FILE_PATH_FOR_TEST, d.instance.maintenance_file_path
|
109
114
|
end
|
110
115
|
def test_configure_with_schemaname
|
111
116
|
d = create_driver(CONFIG_JSON_WITH_SCHEMA)
|
@@ -211,54 +216,45 @@ class RedshiftOutputTest < Test::Unit::TestCase
|
|
211
216
|
d_msgpack.run
|
212
217
|
end
|
213
218
|
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
219
|
+
def setup_redshift_connection_mock(options = {})
|
220
|
+
options ||= {}
|
221
|
+
column_names = options[:column_names] || ['key_a', 'key_b', 'key_c', 'key_d', 'key_e', 'key_f', 'key_g', 'key_h']
|
222
|
+
schema_name = options[:schema_name]
|
223
|
+
table_name = options[:table_name] || 'test_table'
|
224
|
+
exec_sql_proc = options[:exec_sql_proc]
|
220
225
|
|
221
|
-
|
222
|
-
if
|
223
|
-
/\Aselect column_name from INFORMATION_SCHEMA.COLUMNS where
|
226
|
+
column_list_query_regex =
|
227
|
+
if schema_name
|
228
|
+
/\Aselect column_name from INFORMATION_SCHEMA.COLUMNS where table_name = '#{table_name}' and table_schema = '#{schema_name}'/
|
224
229
|
else
|
225
|
-
/\Aselect column_name from INFORMATION_SCHEMA.COLUMNS where table_name = '#{
|
230
|
+
/\Aselect column_name from INFORMATION_SCHEMA.COLUMNS where table_name = '#{table_name}'/
|
226
231
|
end
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
if @target_schema
|
231
|
-
/\Acopy #{@target_schema}.#{@target_table} from/
|
232
|
+
copy_query_regex =
|
233
|
+
if schema_name
|
234
|
+
/\Acopy #{schema_name}.#{table_name} from/
|
232
235
|
else
|
233
|
-
/\Acopy #{
|
236
|
+
/\Acopy #{table_name} from/
|
234
237
|
end
|
235
|
-
end
|
236
238
|
|
237
|
-
|
238
|
-
|
239
|
-
if
|
240
|
-
|
239
|
+
flexmock(Fluent::RedshiftOutput::RedshiftConnection).new_instances do |conn|
|
240
|
+
conn.should_receive(:exec).and_return do |sql, block|
|
241
|
+
if exec_sql_proc
|
242
|
+
exec_sql_proc.call(sql, block)
|
243
|
+
elsif block
|
244
|
+
if sql =~ column_list_query_regex
|
245
|
+
block.call column_names.collect{|key| {'column_name' => key}}
|
246
|
+
else
|
247
|
+
block.call []
|
248
|
+
end
|
241
249
|
else
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
error.result = "ERROR: Load into table '#{@target_table}' failed. Check 'stl_load_errors' system table for details."
|
248
|
-
raise error
|
250
|
+
unless sql =~ copy_query_regex
|
251
|
+
error = PG::Error.new("ERROR: Load into table '#{@target_table}' failed. Check 'stl_load_errors' system table for details.")
|
252
|
+
error.result = "ERROR: Load into table '#{@target_table}' failed. Check 'stl_load_errors' system table for details."
|
253
|
+
raise Fluent::RedshiftOutput::RedshiftError.new(error)
|
254
|
+
end
|
249
255
|
end
|
250
256
|
end
|
251
257
|
end
|
252
|
-
|
253
|
-
def close
|
254
|
-
end
|
255
|
-
end
|
256
|
-
|
257
|
-
def setup_pg_mock
|
258
|
-
# create mock of PG
|
259
|
-
def PG.connect(dbinfo)
|
260
|
-
return PGConnectionMock.new
|
261
|
-
end
|
262
258
|
end
|
263
259
|
|
264
260
|
def setup_s3_mock(expected_data)
|
@@ -305,9 +301,10 @@ class RedshiftOutputTest < Test::Unit::TestCase
|
|
305
301
|
flexmock(Tempfile).new_instances.should_receive(:close!).at_least.once
|
306
302
|
end
|
307
303
|
|
308
|
-
def setup_mocks(expected_data)
|
309
|
-
|
310
|
-
setup_s3_mock(expected_data)
|
304
|
+
def setup_mocks(expected_data, options = {})
|
305
|
+
setup_redshift_connection_mock(options)
|
306
|
+
setup_s3_mock(expected_data)
|
307
|
+
end
|
311
308
|
|
312
309
|
def test_write_with_csv
|
313
310
|
setup_mocks(%[val_a,val_b,val_c,val_d\nval_e,val_f,val_g,val_h\n])
|
@@ -318,7 +315,7 @@ class RedshiftOutputTest < Test::Unit::TestCase
|
|
318
315
|
end
|
319
316
|
|
320
317
|
def test_write_with_json
|
321
|
-
setup_mocks(%[val_a\tval_b\t\t\t\t\t\t\n\t\tval_c\tval_d\t\t\t\t\n])
|
318
|
+
setup_mocks(%[val_a\tval_b\t\\N\t\\N\t\\N\t\\N\t\\N\t\\N\n\\N\t\\N\tval_c\tval_d\t\\N\t\\N\t\\N\t\\N\n])
|
322
319
|
setup_tempfile_mock_to_be_closed
|
323
320
|
d_json = create_driver(CONFIG_JSON)
|
324
321
|
emit_json(d_json)
|
@@ -326,7 +323,7 @@ class RedshiftOutputTest < Test::Unit::TestCase
|
|
326
323
|
end
|
327
324
|
|
328
325
|
def test_write_with_json_hash_value
|
329
|
-
setup_mocks("val_a\t{\"foo\":\"var\"}\t\t\t\t\t\t\n\t\tval_c\tval_d\t\t\t\t\n")
|
326
|
+
setup_mocks("val_a\t{\"foo\":\"var\"}\t\\N\t\\N\t\\N\t\\N\t\\N\t\\N\n\\N\t\\N\tval_c\tval_d\t\\N\t\\N\t\\N\t\\N\n")
|
330
327
|
d_json = create_driver(CONFIG_JSON)
|
331
328
|
d_json.emit({"log" => %[{"key_a" : "val_a", "key_b" : {"foo" : "var"}}]} , DEFAULT_TIME)
|
332
329
|
d_json.emit(RECORD_JSON_B, DEFAULT_TIME)
|
@@ -334,7 +331,7 @@ class RedshiftOutputTest < Test::Unit::TestCase
|
|
334
331
|
end
|
335
332
|
|
336
333
|
def test_write_with_json_array_value
|
337
|
-
setup_mocks("val_a\t[\"foo\",\"var\"]\t\t\t\t\t\t\n\t\tval_c\tval_d\t\t\t\t\n")
|
334
|
+
setup_mocks("val_a\t[\"foo\",\"var\"]\t\\N\t\\N\t\\N\t\\N\t\\N\t\\N\n\\N\t\\N\tval_c\tval_d\t\\N\t\\N\t\\N\t\\N\n")
|
338
335
|
d_json = create_driver(CONFIG_JSON)
|
339
336
|
d_json.emit({"log" => %[{"key_a" : "val_a", "key_b" : ["foo", "var"]}]} , DEFAULT_TIME)
|
340
337
|
d_json.emit(RECORD_JSON_B, DEFAULT_TIME)
|
@@ -342,13 +339,20 @@ class RedshiftOutputTest < Test::Unit::TestCase
|
|
342
339
|
end
|
343
340
|
|
344
341
|
def test_write_with_json_including_tab_newline_quote
|
345
|
-
setup_mocks("val_a_with_\\\t_tab_\\\n_newline\tval_b_with_\\\\_quote\t\t\t\t\t\t\n\t\tval_c\tval_d\t\t\t\t\n")
|
342
|
+
setup_mocks("val_a_with_\\\t_tab_\\\n_newline\tval_b_with_\\\\_quote\t\\N\t\\N\t\\N\t\\N\t\\N\t\\N\n\\N\t\\N\tval_c\tval_d\t\\N\t\\N\t\\N\t\\N\n")
|
346
343
|
d_json = create_driver(CONFIG_JSON)
|
347
344
|
d_json.emit({"log" => %[{"key_a" : "val_a_with_\\t_tab_\\n_newline", "key_b" : "val_b_with_\\\\_quote"}]} , DEFAULT_TIME)
|
348
345
|
d_json.emit(RECORD_JSON_B, DEFAULT_TIME)
|
349
346
|
assert_equal true, d_json.run
|
350
347
|
end
|
351
348
|
|
349
|
+
def test_write_with_json_empty_text_value
|
350
|
+
setup_mocks(%[val_a\t\t\\N\t\\N\t\\N\t\\N\t\\N\t\\N\n])
|
351
|
+
d_json = create_driver(CONFIG_JSON)
|
352
|
+
d_json.emit({"log" => %[{"key_a" : "val_a", "key_b" : ""}]} , DEFAULT_TIME)
|
353
|
+
assert_equal true, d_json.run
|
354
|
+
end
|
355
|
+
|
352
356
|
def test_write_with_json_no_data
|
353
357
|
setup_mocks("")
|
354
358
|
d_json = create_driver(CONFIG_JSON)
|
@@ -358,7 +362,7 @@ class RedshiftOutputTest < Test::Unit::TestCase
|
|
358
362
|
end
|
359
363
|
|
360
364
|
def test_write_with_json_invalid_one_line
|
361
|
-
setup_mocks(%[\t\tval_c\tval_d\t\t\t\t\n])
|
365
|
+
setup_mocks(%[\\N\t\\N\tval_c\tval_d\t\\N\t\\N\t\\N\t\\N\n])
|
362
366
|
d_json = create_driver(CONFIG_JSON)
|
363
367
|
d_json.emit({"log" => %[}}]}, DEFAULT_TIME)
|
364
368
|
d_json.emit(RECORD_JSON_B, DEFAULT_TIME)
|
@@ -366,7 +370,7 @@ class RedshiftOutputTest < Test::Unit::TestCase
|
|
366
370
|
end
|
367
371
|
|
368
372
|
def test_write_with_json_no_available_data
|
369
|
-
setup_mocks(%[val_a\tval_b\t\t\t\t\t\t\n])
|
373
|
+
setup_mocks(%[val_a\tval_b\t\\N\t\\N\t\\N\t\\N\t\\N\t\\N\n])
|
370
374
|
d_json = create_driver(CONFIG_JSON)
|
371
375
|
d_json.emit(RECORD_JSON_A, DEFAULT_TIME)
|
372
376
|
d_json.emit({"log" => %[{"key_o" : "val_o", "key_p" : "val_p"}]}, DEFAULT_TIME)
|
@@ -374,14 +378,14 @@ class RedshiftOutputTest < Test::Unit::TestCase
|
|
374
378
|
end
|
375
379
|
|
376
380
|
def test_write_with_msgpack
|
377
|
-
setup_mocks(%[val_a\tval_b\t\t\t\t\t\t\n\t\tval_c\tval_d\t\t\t\t\n])
|
381
|
+
setup_mocks(%[val_a\tval_b\t\\N\t\\N\t\\N\t\\N\t\\N\t\\N\n\\N\t\\N\tval_c\tval_d\t\\N\t\\N\t\\N\t\\N\n])
|
378
382
|
d_msgpack = create_driver(CONFIG_MSGPACK)
|
379
383
|
emit_msgpack(d_msgpack)
|
380
384
|
assert_equal true, d_msgpack.run
|
381
385
|
end
|
382
386
|
|
383
387
|
def test_write_with_msgpack_hash_value
|
384
|
-
setup_mocks("val_a\t{\"foo\":\"var\"}\t\t\t\t\t\t\n\t\tval_c\tval_d\t\t\t\t\n")
|
388
|
+
setup_mocks("val_a\t{\"foo\":\"var\"}\t\\N\t\\N\t\\N\t\\N\t\\N\t\\N\n\\N\t\\N\tval_c\tval_d\t\\N\t\\N\t\\N\t\\N\n")
|
385
389
|
d_msgpack = create_driver(CONFIG_MSGPACK)
|
386
390
|
d_msgpack.emit({"key_a" => "val_a", "key_b" => {"foo" => "var"}} , DEFAULT_TIME)
|
387
391
|
d_msgpack.emit(RECORD_MSGPACK_B, DEFAULT_TIME)
|
@@ -389,7 +393,7 @@ class RedshiftOutputTest < Test::Unit::TestCase
|
|
389
393
|
end
|
390
394
|
|
391
395
|
def test_write_with_msgpack_array_value
|
392
|
-
setup_mocks("val_a\t[\"foo\",\"var\"]\t\t\t\t\t\t\n\t\tval_c\tval_d\t\t\t\t\n")
|
396
|
+
setup_mocks("val_a\t[\"foo\",\"var\"]\t\\N\t\\N\t\\N\t\\N\t\\N\t\\N\n\\N\t\\N\tval_c\tval_d\t\\N\t\\N\t\\N\t\\N\n")
|
393
397
|
d_msgpack = create_driver(CONFIG_MSGPACK)
|
394
398
|
d_msgpack.emit({"key_a" => "val_a", "key_b" => ["foo", "var"]} , DEFAULT_TIME)
|
395
399
|
d_msgpack.emit(RECORD_MSGPACK_B, DEFAULT_TIME)
|
@@ -397,7 +401,7 @@ class RedshiftOutputTest < Test::Unit::TestCase
|
|
397
401
|
end
|
398
402
|
|
399
403
|
def test_write_with_msgpack_including_tab_newline_quote
|
400
|
-
setup_mocks("val_a_with_\\\t_tab_\\\n_newline\tval_b_with_\\\\_quote\t\t\t\t\t\t\n\t\tval_c\tval_d\t\t\t\t\n")
|
404
|
+
setup_mocks("val_a_with_\\\t_tab_\\\n_newline\tval_b_with_\\\\_quote\t\\N\t\\N\t\\N\t\\N\t\\N\t\\N\n\\N\t\\N\tval_c\tval_d\t\\N\t\\N\t\\N\t\\N\n")
|
401
405
|
d_msgpack = create_driver(CONFIG_MSGPACK)
|
402
406
|
d_msgpack.emit({"key_a" => "val_a_with_\t_tab_\n_newline", "key_b" => "val_b_with_\\_quote"} , DEFAULT_TIME)
|
403
407
|
d_msgpack.emit(RECORD_MSGPACK_B, DEFAULT_TIME)
|
@@ -413,7 +417,7 @@ class RedshiftOutputTest < Test::Unit::TestCase
|
|
413
417
|
end
|
414
418
|
|
415
419
|
def test_write_with_msgpack_no_available_data
|
416
|
-
setup_mocks(%[val_a\tval_b\t\t\t\t\t\t\n])
|
420
|
+
setup_mocks(%[val_a\tval_b\t\\N\t\\N\t\\N\t\\N\t\\N\t\\N\n])
|
417
421
|
d_msgpack = create_driver(CONFIG_MSGPACK)
|
418
422
|
d_msgpack.emit(RECORD_MSGPACK_A, DEFAULT_TIME)
|
419
423
|
d_msgpack.emit({"key_o" => "val_o", "key_p" => "val_p"}, DEFAULT_TIME)
|
@@ -421,38 +425,21 @@ class RedshiftOutputTest < Test::Unit::TestCase
|
|
421
425
|
end
|
422
426
|
|
423
427
|
def test_write_redshift_connection_error
|
424
|
-
|
425
|
-
|
426
|
-
def initialize(return_keys=[]); end
|
427
|
-
def exec(sql)
|
428
|
-
raise PG::Error, "redshift connection error"
|
429
|
-
end
|
430
|
-
def close; end
|
431
|
-
end.new
|
432
|
-
end
|
433
|
-
setup_s3_mock(%[val_a,val_b,val_c,val_d\nval_e,val_f,val_g,val_h\n])
|
434
|
-
|
428
|
+
setup_mocks(%[val_a,val_b,val_c,val_d\nval_e,val_f,val_g,val_h\n],
|
429
|
+
exec_sql_proc: Proc.new {|sql, block| raise Fluent::RedshiftOutput::RedshiftError, "redshift connection error" })
|
435
430
|
d_csv = create_driver
|
436
431
|
emit_csv(d_csv)
|
437
|
-
assert_raise(
|
432
|
+
assert_raise(Fluent::RedshiftOutput::RedshiftError) {
|
438
433
|
d_csv.run
|
439
434
|
}
|
440
435
|
end
|
441
436
|
|
442
437
|
def test_write_redshift_load_error
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
error = PG::Error.new("ERROR: Load into table 'apache_log' failed. Check 'stl_load_errors' system table for details.")
|
449
|
-
error.result = "ERROR: Load into table 'apache_log' failed. Check 'stl_load_errors' system table for details."
|
450
|
-
raise error
|
451
|
-
end
|
452
|
-
def close; end
|
453
|
-
end.new
|
454
|
-
end
|
455
|
-
setup_s3_mock(%[val_a,val_b,val_c,val_d\nval_e,val_f,val_g,val_h\n])
|
438
|
+
setup_mocks(%[val_a,val_b,val_c,val_d\nval_e,val_f,val_g,val_h\n],
|
439
|
+
exec_sql_proc: Proc.new {|sql, block|
|
440
|
+
msg = "ERROR: Load into table 'apache_log' failed. Check 'stl_load_errors' system table for details."
|
441
|
+
raise Fluent::RedshiftOutput::RedshiftError.new(msg)
|
442
|
+
})
|
456
443
|
|
457
444
|
d_csv = create_driver
|
458
445
|
emit_csv(d_csv)
|
@@ -460,36 +447,19 @@ class RedshiftOutputTest < Test::Unit::TestCase
|
|
460
447
|
end
|
461
448
|
|
462
449
|
def test_write_with_json_redshift_connection_error
|
463
|
-
|
464
|
-
|
465
|
-
def initialize(return_keys=[]); end
|
466
|
-
def exec(sql, &block)
|
467
|
-
error = PG::Error.new("redshift connection error")
|
468
|
-
raise error
|
469
|
-
end
|
470
|
-
def close; end
|
471
|
-
end.new
|
472
|
-
end
|
473
|
-
setup_s3_mock(%[val_a,val_b,val_c,val_d\nval_e,val_f,val_g,val_h\n])
|
450
|
+
setup_mocks(%[val_a,val_b,val_c,val_d\nval_e,val_f,val_g,val_h\n],
|
451
|
+
exec_sql_proc: Proc.new {|sql, block| raise Fluent::RedshiftOutput::RedshiftError.new("redshift connection error")})
|
474
452
|
|
475
453
|
d_json = create_driver(CONFIG_JSON)
|
476
454
|
emit_json(d_json)
|
477
|
-
assert_raise(
|
455
|
+
assert_raise(Fluent::RedshiftOutput::RedshiftError) {
|
478
456
|
d_json.run
|
479
457
|
}
|
480
458
|
end
|
481
459
|
|
482
460
|
def test_write_with_json_no_table_on_redshift
|
483
|
-
|
484
|
-
|
485
|
-
def initialize(return_keys=[]); end
|
486
|
-
def exec(sql, &block)
|
487
|
-
yield [] if block_given?
|
488
|
-
end
|
489
|
-
def close; end
|
490
|
-
end.new
|
491
|
-
end
|
492
|
-
setup_s3_mock(%[val_a,val_b,val_c,val_d\nval_e,val_f,val_g,val_h\n])
|
461
|
+
setup_mocks(%[val_a,val_b,val_c,val_d\nval_e,val_f,val_g,val_h\n],
|
462
|
+
exec_sql_proc: Proc.new {|sql, block| block.call [] if block })
|
493
463
|
|
494
464
|
d_json = create_driver(CONFIG_JSON)
|
495
465
|
emit_json(d_json)
|
@@ -497,15 +467,7 @@ class RedshiftOutputTest < Test::Unit::TestCase
|
|
497
467
|
end
|
498
468
|
|
499
469
|
def test_write_with_json_failed_to_get_columns
|
500
|
-
|
501
|
-
return Class.new do
|
502
|
-
def initialize(return_keys=[]); end
|
503
|
-
def exec(sql, &block)
|
504
|
-
end
|
505
|
-
def close; end
|
506
|
-
end.new
|
507
|
-
end
|
508
|
-
setup_s3_mock("")
|
470
|
+
setup_mocks("", exec_sql_proc: Proc.new {|sql, block| nil})
|
509
471
|
|
510
472
|
d_json = create_driver(CONFIG_JSON)
|
511
473
|
emit_json(d_json)
|
@@ -515,12 +477,21 @@ class RedshiftOutputTest < Test::Unit::TestCase
|
|
515
477
|
end
|
516
478
|
|
517
479
|
def test_write_with_json_fetch_column_with_schema
|
518
|
-
|
519
|
-
|
520
|
-
end
|
521
|
-
setup_s3_mock(%[val_a\tval_b\t\t\t\t\t\t\n\t\tval_c\tval_d\t\t\t\t\n])
|
480
|
+
setup_mocks(%[val_a\tval_b\t\\N\t\\N\t\\N\t\\N\t\\N\t\\N\n\\N\t\\N\tval_c\tval_d\t\\N\t\\N\t\\N\t\\N\n],
|
481
|
+
schema_name: 'test_schema')
|
522
482
|
d_json = create_driver(CONFIG_JSON_WITH_SCHEMA)
|
523
483
|
emit_json(d_json)
|
524
484
|
assert_equal true, d_json.run
|
525
485
|
end
|
486
|
+
|
487
|
+
def test_maintenance_mode
|
488
|
+
flexmock(File).should_receive(:exists?).with(MAINTENANCE_FILE_PATH_FOR_TEST).and_return(true)
|
489
|
+
|
490
|
+
d_json = create_driver(CONFIG_JSON)
|
491
|
+
emit_json(d_json)
|
492
|
+
assert_raise(Fluent::RedshiftOutput::MaintenanceError,
|
493
|
+
"Service is in maintenance mode - maintenance_file_path:#{MAINTENANCE_FILE_PATH_FOR_TEST}") {
|
494
|
+
d_json.run
|
495
|
+
}
|
496
|
+
end
|
526
497
|
end
|
metadata
CHANGED
@@ -1,142 +1,131 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fluent-plugin-redshift-out
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
5
|
-
prerelease:
|
4
|
+
version: 0.4.0
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Naohiro Sakuma
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date: 2015-
|
11
|
+
date: 2015-12-28 00:00:00.000000000 Z
|
13
12
|
dependencies:
|
14
13
|
- !ruby/object:Gem::Dependency
|
15
14
|
name: fluentd
|
16
15
|
requirement: !ruby/object:Gem::Requirement
|
17
|
-
none: false
|
18
16
|
requirements:
|
19
|
-
- -
|
17
|
+
- - ">="
|
20
18
|
- !ruby/object:Gem::Version
|
21
19
|
version: 0.10.0
|
20
|
+
- - "<"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '2'
|
22
23
|
type: :runtime
|
23
24
|
prerelease: false
|
24
25
|
version_requirements: !ruby/object:Gem::Requirement
|
25
|
-
none: false
|
26
26
|
requirements:
|
27
|
-
- -
|
27
|
+
- - ">="
|
28
28
|
- !ruby/object:Gem::Version
|
29
29
|
version: 0.10.0
|
30
|
+
- - "<"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '2'
|
30
33
|
- !ruby/object:Gem::Dependency
|
31
|
-
name: aws-sdk
|
34
|
+
name: aws-sdk-v1
|
32
35
|
requirement: !ruby/object:Gem::Requirement
|
33
|
-
none: false
|
34
36
|
requirements:
|
35
|
-
- -
|
37
|
+
- - ">="
|
36
38
|
- !ruby/object:Gem::Version
|
37
|
-
version:
|
39
|
+
version: 1.6.3
|
38
40
|
type: :runtime
|
39
41
|
prerelease: false
|
40
42
|
version_requirements: !ruby/object:Gem::Requirement
|
41
|
-
none: false
|
42
43
|
requirements:
|
43
|
-
- -
|
44
|
+
- - ">="
|
44
45
|
- !ruby/object:Gem::Version
|
45
|
-
version:
|
46
|
+
version: 1.6.3
|
46
47
|
- !ruby/object:Gem::Dependency
|
47
48
|
name: multi_json
|
48
49
|
requirement: !ruby/object:Gem::Requirement
|
49
|
-
none: false
|
50
50
|
requirements:
|
51
|
-
- - ~>
|
51
|
+
- - "~>"
|
52
52
|
- !ruby/object:Gem::Version
|
53
53
|
version: '1.10'
|
54
54
|
type: :runtime
|
55
55
|
prerelease: false
|
56
56
|
version_requirements: !ruby/object:Gem::Requirement
|
57
|
-
none: false
|
58
57
|
requirements:
|
59
|
-
- - ~>
|
58
|
+
- - "~>"
|
60
59
|
- !ruby/object:Gem::Version
|
61
60
|
version: '1.10'
|
62
61
|
- !ruby/object:Gem::Dependency
|
63
62
|
name: yajl-ruby
|
64
63
|
requirement: !ruby/object:Gem::Requirement
|
65
|
-
none: false
|
66
64
|
requirements:
|
67
|
-
- - ~>
|
65
|
+
- - "~>"
|
68
66
|
- !ruby/object:Gem::Version
|
69
67
|
version: '1.2'
|
70
68
|
type: :runtime
|
71
69
|
prerelease: false
|
72
70
|
version_requirements: !ruby/object:Gem::Requirement
|
73
|
-
none: false
|
74
71
|
requirements:
|
75
|
-
- - ~>
|
72
|
+
- - "~>"
|
76
73
|
- !ruby/object:Gem::Version
|
77
74
|
version: '1.2'
|
78
75
|
- !ruby/object:Gem::Dependency
|
79
76
|
name: pg
|
80
77
|
requirement: !ruby/object:Gem::Requirement
|
81
|
-
none: false
|
82
78
|
requirements:
|
83
|
-
- - ~>
|
79
|
+
- - "~>"
|
84
80
|
- !ruby/object:Gem::Version
|
85
81
|
version: 0.17.0
|
86
82
|
type: :runtime
|
87
83
|
prerelease: false
|
88
84
|
version_requirements: !ruby/object:Gem::Requirement
|
89
|
-
none: false
|
90
85
|
requirements:
|
91
|
-
- - ~>
|
86
|
+
- - "~>"
|
92
87
|
- !ruby/object:Gem::Version
|
93
88
|
version: 0.17.0
|
94
89
|
- !ruby/object:Gem::Dependency
|
95
90
|
name: rake
|
96
91
|
requirement: !ruby/object:Gem::Requirement
|
97
|
-
none: false
|
98
92
|
requirements:
|
99
|
-
- -
|
93
|
+
- - ">="
|
100
94
|
- !ruby/object:Gem::Version
|
101
95
|
version: '0'
|
102
96
|
type: :development
|
103
97
|
prerelease: false
|
104
98
|
version_requirements: !ruby/object:Gem::Requirement
|
105
|
-
none: false
|
106
99
|
requirements:
|
107
|
-
- -
|
100
|
+
- - ">="
|
108
101
|
- !ruby/object:Gem::Version
|
109
102
|
version: '0'
|
110
103
|
- !ruby/object:Gem::Dependency
|
111
104
|
name: simplecov
|
112
105
|
requirement: !ruby/object:Gem::Requirement
|
113
|
-
none: false
|
114
106
|
requirements:
|
115
|
-
- -
|
107
|
+
- - ">="
|
116
108
|
- !ruby/object:Gem::Version
|
117
109
|
version: 0.5.4
|
118
110
|
type: :development
|
119
111
|
prerelease: false
|
120
112
|
version_requirements: !ruby/object:Gem::Requirement
|
121
|
-
none: false
|
122
113
|
requirements:
|
123
|
-
- -
|
114
|
+
- - ">="
|
124
115
|
- !ruby/object:Gem::Version
|
125
116
|
version: 0.5.4
|
126
117
|
- !ruby/object:Gem::Dependency
|
127
118
|
name: flexmock
|
128
119
|
requirement: !ruby/object:Gem::Requirement
|
129
|
-
none: false
|
130
120
|
requirements:
|
131
|
-
- -
|
121
|
+
- - ">="
|
132
122
|
- !ruby/object:Gem::Version
|
133
123
|
version: 1.3.1
|
134
124
|
type: :development
|
135
125
|
prerelease: false
|
136
126
|
version_requirements: !ruby/object:Gem::Requirement
|
137
|
-
none: false
|
138
127
|
requirements:
|
139
|
-
- -
|
128
|
+
- - ">="
|
140
129
|
- !ruby/object:Gem::Version
|
141
130
|
version: 1.3.1
|
142
131
|
description: Amazon Redshift output plugin for Fluentd
|
@@ -146,7 +135,7 @@ executables: []
|
|
146
135
|
extensions: []
|
147
136
|
extra_rdoc_files: []
|
148
137
|
files:
|
149
|
-
- .gitignore
|
138
|
+
- ".gitignore"
|
150
139
|
- Gemfile
|
151
140
|
- README.md
|
152
141
|
- Rakefile
|
@@ -157,27 +146,26 @@ files:
|
|
157
146
|
- test/test_helper.rb
|
158
147
|
homepage: https://github.com/sakuma/fluent-plugin-redshift-out
|
159
148
|
licenses: []
|
149
|
+
metadata: {}
|
160
150
|
post_install_message:
|
161
151
|
rdoc_options: []
|
162
152
|
require_paths:
|
163
153
|
- lib
|
164
154
|
required_ruby_version: !ruby/object:Gem::Requirement
|
165
|
-
none: false
|
166
155
|
requirements:
|
167
|
-
- -
|
156
|
+
- - ">="
|
168
157
|
- !ruby/object:Gem::Version
|
169
158
|
version: '0'
|
170
159
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
171
|
-
none: false
|
172
160
|
requirements:
|
173
|
-
- -
|
161
|
+
- - ">="
|
174
162
|
- !ruby/object:Gem::Version
|
175
163
|
version: '0'
|
176
164
|
requirements: []
|
177
165
|
rubyforge_project:
|
178
|
-
rubygems_version:
|
166
|
+
rubygems_version: 2.4.5.1
|
179
167
|
signing_key:
|
180
|
-
specification_version:
|
168
|
+
specification_version: 4
|
181
169
|
summary: Amazon Redshift output plugin for Fluentd
|
182
170
|
test_files:
|
183
171
|
- test/plugin/test_out_redshift.rb
|