fluent-plugin-s3 1.8.3 → 1.8.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/dependabot.yml +17 -0
- data/.github/workflows/linux.yml +13 -4
- data/.github/workflows/stale-actions.yml +4 -1
- data/ChangeLog +13 -0
- data/README.md +1 -2
- data/VERSION +1 -1
- data/docs/credentials.md +17 -0
- data/docs/input.md +5 -0
- data/docs/output.md +2 -1
- data/fluent-plugin-s3.gemspec +2 -1
- data/lib/fluent/plugin/in_s3.rb +73 -11
- data/lib/fluent/plugin/out_s3.rb +19 -2
- data/lib/fluent/plugin/s3_compressor_gzip_command.rb +5 -0
- data/lib/fluent/plugin/s3_compressor_zstd.rb +5 -0
- data/lib/fluent/plugin/s3_extractor_gzip_command.rb +9 -20
- data/lib/fluent/plugin/s3_extractor_lzma2.rb +5 -13
- data/lib/fluent/plugin/s3_extractor_lzo.rb +5 -13
- data/test/test_in_s3.rb +123 -12
- data/test/test_out_s3.rb +144 -0
- metadata +22 -9
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4ad2143d493bcb5b5805df10225cdb068bffc8ae24a1a6838bde343b18a845ed
|
|
4
|
+
data.tar.gz: faf67f2a0e65e73b385944113a485f509365b3b9f1e8059ef1b06df8c6719607
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 51c6cd59240d0d2055260b779e9887ab5a8fc0839246cf9154566271de72e30a73ba0587ae2945dd275c1b16b71c43e165a77e43419e73279fbb938904790a48
|
|
7
|
+
data.tar.gz: ff8aa7a1c32cf273527165eee2dec5df4df217d20e5047c53908469bf13fd8baf6b803781e30d09126d83e0ba4658bf8b28ae2b034e403f627ae9a9e98b312a3
|
data/.github/dependabot.yml
CHANGED
|
@@ -4,3 +4,20 @@ updates:
|
|
|
4
4
|
directory: '/'
|
|
5
5
|
schedule:
|
|
6
6
|
interval: 'monthly'
|
|
7
|
+
groups:
|
|
8
|
+
# PR: "Security update [package] from [old] to [new]"
|
|
9
|
+
# This PR should be merged in hurry
|
|
10
|
+
security-updates:
|
|
11
|
+
applies-to: security-updates
|
|
12
|
+
patterns:
|
|
13
|
+
- '*'
|
|
14
|
+
|
|
15
|
+
# PR: "Bump [package] from [old] to [new]"
|
|
16
|
+
# No need to be merged this PR in hurry. It is enough to merge
|
|
17
|
+
# once in a month.
|
|
18
|
+
monthly-updates:
|
|
19
|
+
applies-to: version-updates
|
|
20
|
+
patterns:
|
|
21
|
+
- '*'
|
|
22
|
+
# Allow to create PR both of security and normal updates.
|
|
23
|
+
open-pull-requests-limit: 1
|
data/.github/workflows/linux.yml
CHANGED
|
@@ -4,25 +4,34 @@ on:
|
|
|
4
4
|
branches: [master]
|
|
5
5
|
pull_request:
|
|
6
6
|
branches: [master]
|
|
7
|
+
schedule:
|
|
8
|
+
- cron: '0 0 1 * *'
|
|
7
9
|
jobs:
|
|
10
|
+
ruby-versions:
|
|
11
|
+
uses: ruby/actions/.github/workflows/ruby_versions.yml@master
|
|
12
|
+
with:
|
|
13
|
+
engine: cruby
|
|
14
|
+
min_version: 2.7
|
|
8
15
|
build:
|
|
16
|
+
needs: ruby-versions
|
|
9
17
|
runs-on: ${{ matrix.os }}
|
|
10
18
|
strategy:
|
|
11
19
|
fail-fast: false
|
|
12
20
|
matrix:
|
|
13
|
-
ruby:
|
|
21
|
+
ruby: ${{ fromJson(needs.ruby-versions.outputs.versions) }}
|
|
22
|
+
exclude:
|
|
23
|
+
- ruby: head
|
|
14
24
|
os:
|
|
15
25
|
- ubuntu-latest
|
|
16
26
|
name: Ruby ${{ matrix.ruby }} unit testing on ${{ matrix.os }}
|
|
17
27
|
steps:
|
|
18
|
-
- uses: actions/checkout@
|
|
19
|
-
- uses: ruby/setup-ruby@v1
|
|
28
|
+
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
|
29
|
+
- uses: ruby/setup-ruby@afeafc3d1ab54a631816aba4c914a0081c12ff2f # v1.310.0
|
|
20
30
|
with:
|
|
21
31
|
ruby-version: ${{ matrix.ruby }}
|
|
22
32
|
- name: unit testing
|
|
23
33
|
env:
|
|
24
34
|
CI: true
|
|
25
35
|
run: |
|
|
26
|
-
gem install rake
|
|
27
36
|
bundle install --jobs 4 --retry 3
|
|
28
37
|
bundle exec rake test
|
|
@@ -6,8 +6,11 @@ on:
|
|
|
6
6
|
jobs:
|
|
7
7
|
stale:
|
|
8
8
|
runs-on: ubuntu-latest
|
|
9
|
+
permissions:
|
|
10
|
+
issues: write
|
|
11
|
+
pull-requests: write
|
|
9
12
|
steps:
|
|
10
|
-
- uses: actions/stale@
|
|
13
|
+
- uses: actions/stale@eb5cf3af3ac0a1aa4c9c45633dd1ae542a27a899 # v10.3.0
|
|
11
14
|
with:
|
|
12
15
|
repo-token: ${{ secrets.GITHUB_TOKEN }}
|
|
13
16
|
days-before-stale: 30
|
data/ChangeLog
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
Release 1.8.5 - 2026/06/25
|
|
2
|
+
|
|
3
|
+
* in_s3: enforce size limits on decompressed payloads
|
|
4
|
+
|
|
5
|
+
Release 1.8.4 - 2026/03/04
|
|
6
|
+
|
|
7
|
+
* in_s3: add aws_profile / aws_credential_process parameters for credencials (GitHub: #464)
|
|
8
|
+
* out_s3: add aws_profile / aws_credential_process parameters for credencials (GitHub: #464)
|
|
9
|
+
* out_s3: stop unnecessary re-compression when chunk is already compressed (GitHub: #460)
|
|
10
|
+
* gem: use same zstd-ruby version with Fluentd (GitHub: #463)
|
|
11
|
+
* gem: add ostruct gem as dependency for Ruby 4.0 (GitHub: #461)
|
|
12
|
+
* docs: add missing zstd compression (GitHub: #459)
|
|
13
|
+
|
|
1
14
|
Release 1.8.3 - 2025/02/18
|
|
2
15
|
|
|
3
16
|
* out_s3: Add `sts_http_proxy` and `sts_endpoint_url` to web_identity_credentials (GitHub: #452)
|
data/README.md
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# Amazon S3 plugin for [Fluentd](http://github.com/fluent/fluentd)
|
|
2
2
|
|
|
3
|
-
[
|
|
4
|
-
alt="Build Status" />](https://travis-ci.org/fluent/fluent-plugin-s3) [<img
|
|
3
|
+
[](https://github.com/fluent/fluent-plugin-s3/actions/workflows/linux.yml) [<img
|
|
5
4
|
src="https://codeclimate.com/github/fluent/fluent-plugin-s3/badges/gpa.svg"
|
|
6
5
|
/>](https://codeclimate.com/github/fluent/fluent-plugin-s3)
|
|
7
6
|
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
1.8.
|
|
1
|
+
1.8.5
|
data/docs/credentials.md
CHANGED
|
@@ -14,6 +14,23 @@ AWS access key id.
|
|
|
14
14
|
|
|
15
15
|
AWS secret key.
|
|
16
16
|
|
|
17
|
+
### aws_profile
|
|
18
|
+
|
|
19
|
+
Specify a named profile from your shared AWS configuration files (`~/.aws/credentials` or `~/.aws/config`).
|
|
20
|
+
This is useful for environments using IAM Roles or short-lived credentials.
|
|
21
|
+
|
|
22
|
+
### aws_credential_process
|
|
23
|
+
|
|
24
|
+
Specify a `credential_process` command directly in the Fluentd configuration.
|
|
25
|
+
This enables integration with external credential helpers like IAM Roles Anywhere.
|
|
26
|
+
|
|
27
|
+
<match *>
|
|
28
|
+
@type s3
|
|
29
|
+
# Example for IAM Roles Anywhere
|
|
30
|
+
aws_credential_process "/path/to/aws_signing_helper credential-process --certificate /path/to/cert.pem --private-key /path/to/key.pem --trust-anchor-arn <ARN> --profile-arn <ARN> --role-arn <ARN>"
|
|
31
|
+
# ... other settings
|
|
32
|
+
</match>
|
|
33
|
+
|
|
17
34
|
## \<assume_role_credentials\> section
|
|
18
35
|
|
|
19
36
|
Typically, you use AssumeRole for cross-account access or federation.
|
data/docs/input.md
CHANGED
|
@@ -29,6 +29,11 @@ See also [Configuration: credentials](credentials.md) for common comprehensive p
|
|
|
29
29
|
|
|
30
30
|
Whether or not object metadata should be added to the record. Defaults to `false`. See below for details.
|
|
31
31
|
|
|
32
|
+
## decompression_size_limit
|
|
33
|
+
|
|
34
|
+
The size limit of the decompressed data. The default is `256m` (256 MiB).
|
|
35
|
+
This parameter is designed to prevent memory exhaustion when extracting highly compressed objects from S3.
|
|
36
|
+
|
|
32
37
|
## match_regexp
|
|
33
38
|
|
|
34
39
|
If provided, process the S3 object only if its keys matches the regular expression
|
data/docs/output.md
CHANGED
|
@@ -185,6 +185,7 @@ archive format on S3. You can use several format:
|
|
|
185
185
|
* parquet (Need columnify command)
|
|
186
186
|
* This compressor uses an external [columnify](https://github.com/reproio/columnify) command.
|
|
187
187
|
* Use [`<compress>`](#compress-for-parquet-compressor-only) section to configure columnify command behavior.
|
|
188
|
+
* zstd (Need `zstd-ruby` gem, supported since v1.8.0)
|
|
188
189
|
|
|
189
190
|
See [Use your compression algorithm](howto.md#use-your-compression-algorighm) section for adding another format.
|
|
190
191
|
|
|
@@ -208,7 +209,7 @@ parquet file page size. default: 8192 bytes
|
|
|
208
209
|
|
|
209
210
|
### parquet_row_group_size
|
|
210
211
|
|
|
211
|
-
parquet file row group size. default: 128
|
|
212
|
+
parquet file row group size. default: 128 MiB
|
|
212
213
|
|
|
213
214
|
### record_type
|
|
214
215
|
|
data/fluent-plugin-s3.gemspec
CHANGED
|
@@ -23,8 +23,9 @@ Gem::Specification.new do |gem|
|
|
|
23
23
|
gem.add_development_dependency "test-unit", ">= 3.0.8"
|
|
24
24
|
gem.add_development_dependency "test-unit-rr", ">= 1.0.3"
|
|
25
25
|
gem.add_development_dependency "timecop"
|
|
26
|
+
gem.add_development_dependency "ostruct"
|
|
26
27
|
# aws-sdk-core requires one of ox, oga, libxml, nokogiri or rexml,
|
|
27
28
|
# and rexml is no longer default gem as of Ruby 3.0.
|
|
28
29
|
gem.add_development_dependency "rexml"
|
|
29
|
-
gem.add_development_dependency "zstd-ruby"
|
|
30
|
+
gem.add_development_dependency "zstd-ruby", ["~> 1.5"]
|
|
30
31
|
end
|
data/lib/fluent/plugin/in_s3.rb
CHANGED
|
@@ -8,6 +8,7 @@ require 'cgi/util'
|
|
|
8
8
|
require 'zlib'
|
|
9
9
|
require 'time'
|
|
10
10
|
require 'tempfile'
|
|
11
|
+
require 'shellwords'
|
|
11
12
|
|
|
12
13
|
module Fluent::Plugin
|
|
13
14
|
class S3Input < Input
|
|
@@ -21,15 +22,22 @@ module Fluent::Plugin
|
|
|
21
22
|
end
|
|
22
23
|
|
|
23
24
|
DEFAULT_PARSE_TYPE = "none"
|
|
25
|
+
DECOMPRESSION_SIZE_LIMIT = 256 * 1024 * 1024
|
|
24
26
|
|
|
25
27
|
desc "Use aws-sdk-ruby bundled cert"
|
|
26
28
|
config_param :use_bundled_cert, :bool, default: false
|
|
27
29
|
desc "Add object metadata to the records parsed out of a given object"
|
|
28
30
|
config_param :add_object_metadata, :bool, default: false
|
|
31
|
+
desc 'The size limit of the extracted element.'
|
|
32
|
+
config_param :decompression_size_limit, :size, default: DECOMPRESSION_SIZE_LIMIT
|
|
29
33
|
desc "AWS access key id"
|
|
30
34
|
config_param :aws_key_id, :string, default: nil, secret: true
|
|
31
35
|
desc "AWS secret key."
|
|
32
36
|
config_param :aws_sec_key, :string, default: nil, secret: true
|
|
37
|
+
desc "AWS profile name."
|
|
38
|
+
config_param :aws_profile, :string, default: nil
|
|
39
|
+
desc "The command to run to generate credentials."
|
|
40
|
+
config_param :aws_credential_process, :string, default: nil
|
|
33
41
|
config_section :assume_role_credentials, multi: false do
|
|
34
42
|
desc "The Amazon Resource Name (ARN) of the role to assume"
|
|
35
43
|
config_param :role_arn, :string
|
|
@@ -154,7 +162,7 @@ module Fluent::Plugin
|
|
|
154
162
|
|
|
155
163
|
Aws.use_bundled_cert! if @use_bundled_cert
|
|
156
164
|
|
|
157
|
-
@extractor = EXTRACTOR_REGISTRY.lookup(@store_as).new(log: log)
|
|
165
|
+
@extractor = EXTRACTOR_REGISTRY.lookup(@store_as).new(log: log, decompression_size_limit: @decompression_size_limit)
|
|
158
166
|
@extractor.configure(conf)
|
|
159
167
|
|
|
160
168
|
@parser = parser_create(conf: parser_config, default_type: DEFAULT_PARSE_TYPE)
|
|
@@ -205,13 +213,13 @@ module Fluent::Plugin
|
|
|
205
213
|
begin
|
|
206
214
|
@poller.poll(options) do |message|
|
|
207
215
|
begin
|
|
208
|
-
body =
|
|
216
|
+
body = JSON.parse(message.body)
|
|
209
217
|
log.debug(body)
|
|
210
218
|
next unless is_valid_queue(body) # skip test queue
|
|
211
219
|
if @match_regexp
|
|
212
220
|
raw_key = get_raw_key(body)
|
|
213
221
|
key = CGI.unescape(raw_key)
|
|
214
|
-
next unless @match_regexp.match?(key)
|
|
222
|
+
next unless @match_regexp.match?(key)
|
|
215
223
|
end
|
|
216
224
|
process(body)
|
|
217
225
|
rescue => e
|
|
@@ -231,7 +239,7 @@ module Fluent::Plugin
|
|
|
231
239
|
if @sqs.event_bridge_mode
|
|
232
240
|
log.debug("checking for eventbridge property")
|
|
233
241
|
!!body["detail"]
|
|
234
|
-
else
|
|
242
|
+
else
|
|
235
243
|
log.debug("checking for Records property")
|
|
236
244
|
!!body["Records"]
|
|
237
245
|
end
|
|
@@ -242,7 +250,7 @@ module Fluent::Plugin
|
|
|
242
250
|
body["detail"]["object"]["key"]
|
|
243
251
|
else
|
|
244
252
|
body["Records"].first["s3"]["object"]["key"]
|
|
245
|
-
end
|
|
253
|
+
end
|
|
246
254
|
end
|
|
247
255
|
|
|
248
256
|
def setup_credentials
|
|
@@ -252,6 +260,10 @@ module Fluent::Plugin
|
|
|
252
260
|
when @aws_key_id && @aws_sec_key
|
|
253
261
|
options[:access_key_id] = @aws_key_id
|
|
254
262
|
options[:secret_access_key] = @aws_sec_key
|
|
263
|
+
when @aws_profile
|
|
264
|
+
options[:profile] = @aws_profile
|
|
265
|
+
when @aws_credential_process
|
|
266
|
+
options[:credentials] = Aws::ProcessCredentials.new(Shellwords.split(@aws_credential_process))
|
|
255
267
|
when @assume_role_credentials
|
|
256
268
|
c = @assume_role_credentials
|
|
257
269
|
credentials_options[:role_arn] = c.role_arn
|
|
@@ -356,13 +368,18 @@ module Fluent::Plugin
|
|
|
356
368
|
end
|
|
357
369
|
|
|
358
370
|
class Extractor
|
|
371
|
+
class SizeLimitError < StandardError; end
|
|
372
|
+
|
|
359
373
|
include Fluent::Configurable
|
|
360
374
|
|
|
361
375
|
attr_reader :log
|
|
362
376
|
|
|
363
|
-
|
|
377
|
+
BYTES_TO_READ = 64 * 1024
|
|
378
|
+
|
|
379
|
+
def initialize(log: $log, decompression_size_limit: DECOMPRESSION_SIZE_LIMIT, **options)
|
|
364
380
|
super()
|
|
365
381
|
@log = log
|
|
382
|
+
@decompression_size_limit = decompression_size_limit
|
|
366
383
|
end
|
|
367
384
|
|
|
368
385
|
def configure(conf)
|
|
@@ -390,6 +407,38 @@ module Fluent::Plugin
|
|
|
390
407
|
raise Fluent::ConfigError, "'#{command}' utility must be in PATH for #{algo} compression"
|
|
391
408
|
end
|
|
392
409
|
end
|
|
410
|
+
|
|
411
|
+
def extract_with_command(command, io, tempfile_basename = "s3-extractor-tmp")
|
|
412
|
+
path = if io.respond_to?(:path)
|
|
413
|
+
io.path
|
|
414
|
+
else
|
|
415
|
+
extractor = TextExtractor.new(log: log, decompression_size_limit: @decompression_size_limit)
|
|
416
|
+
temp = Tempfile.new(tempfile_basename)
|
|
417
|
+
temp.write(extractor.extract(io))
|
|
418
|
+
temp.close
|
|
419
|
+
temp.path
|
|
420
|
+
end
|
|
421
|
+
|
|
422
|
+
out = ''
|
|
423
|
+
begin
|
|
424
|
+
Open3.popen3("#{command} #{path}") do |stdin, stdout, stderr, wait_thr|
|
|
425
|
+
stdin.close
|
|
426
|
+
while (chunk = stdout.read(BYTES_TO_READ))
|
|
427
|
+
out << chunk
|
|
428
|
+
if out.bytesize > @decompression_size_limit
|
|
429
|
+
Process.kill("TERM", wait_thr.pid) rescue nil
|
|
430
|
+
raise SizeLimitError, "Extracted data exceeds limit of #{@decompression_size_limit} bytes"
|
|
431
|
+
end
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
if wait_thr.value.success?
|
|
435
|
+
out
|
|
436
|
+
else
|
|
437
|
+
raise "Command execution failed: #{command} (status: #{wait_thr.value})"
|
|
438
|
+
end
|
|
439
|
+
end
|
|
440
|
+
end
|
|
441
|
+
end
|
|
393
442
|
end
|
|
394
443
|
|
|
395
444
|
class GzipExtractor < Extractor
|
|
@@ -405,19 +454,25 @@ module Fluent::Plugin
|
|
|
405
454
|
# https://bugs.ruby-lang.org/issues/11180
|
|
406
455
|
# https://github.com/exAspArk/multiple_files_gzip_reader
|
|
407
456
|
def extract(io)
|
|
408
|
-
|
|
457
|
+
out = ''
|
|
409
458
|
loop do
|
|
410
459
|
unused = nil
|
|
411
460
|
Zlib::GzipReader.wrap(io) do |gz|
|
|
412
|
-
|
|
461
|
+
while (chunk = gz.read(BYTES_TO_READ))
|
|
462
|
+
out << chunk
|
|
463
|
+
if out.bytesize > @decompression_size_limit
|
|
464
|
+
raise SizeLimitError, "Extracted data exceeds limit of #{@decompression_size_limit} bytes"
|
|
465
|
+
end
|
|
466
|
+
end
|
|
413
467
|
unused = gz.unused
|
|
414
468
|
gz.finish
|
|
415
469
|
end
|
|
416
470
|
io.pos -= unused ? unused.length : 0
|
|
417
471
|
break if io.eof?
|
|
418
472
|
end
|
|
419
|
-
|
|
420
|
-
|
|
473
|
+
out
|
|
474
|
+
ensure
|
|
475
|
+
io.close unless io.closed?
|
|
421
476
|
end
|
|
422
477
|
end
|
|
423
478
|
|
|
@@ -431,7 +486,14 @@ module Fluent::Plugin
|
|
|
431
486
|
end
|
|
432
487
|
|
|
433
488
|
def extract(io)
|
|
434
|
-
|
|
489
|
+
out = ''
|
|
490
|
+
while (chunk = io.read(BYTES_TO_READ))
|
|
491
|
+
out << chunk
|
|
492
|
+
if out.bytesize > @decompression_size_limit
|
|
493
|
+
raise SizeLimitError, "Extracted data exceeds limit of #{@decompression_size_limit} bytes"
|
|
494
|
+
end
|
|
495
|
+
end
|
|
496
|
+
out
|
|
435
497
|
end
|
|
436
498
|
end
|
|
437
499
|
|
data/lib/fluent/plugin/out_s3.rb
CHANGED
|
@@ -6,6 +6,7 @@ require 'zlib'
|
|
|
6
6
|
require 'time'
|
|
7
7
|
require 'tempfile'
|
|
8
8
|
require 'securerandom'
|
|
9
|
+
require 'shellwords'
|
|
9
10
|
|
|
10
11
|
module Fluent::Plugin
|
|
11
12
|
class S3Output < Output
|
|
@@ -29,6 +30,10 @@ module Fluent::Plugin
|
|
|
29
30
|
config_param :aws_key_id, :string, default: nil, secret: true
|
|
30
31
|
desc "AWS secret key."
|
|
31
32
|
config_param :aws_sec_key, :string, default: nil, secret: true
|
|
33
|
+
desc "AWS profile name."
|
|
34
|
+
config_param :aws_profile, :string, default: nil
|
|
35
|
+
desc "The command to run to generate credentials."
|
|
36
|
+
config_param :aws_credential_process, :string, default: nil
|
|
32
37
|
config_section :assume_role_credentials, multi: false do
|
|
33
38
|
desc "The Amazon Resource Name (ARN) of the role to assume"
|
|
34
39
|
config_param :role_arn, :string, secret: true
|
|
@@ -203,7 +208,7 @@ module Fluent::Plugin
|
|
|
203
208
|
|
|
204
209
|
begin
|
|
205
210
|
buffer_type = @buffer_config[:@type]
|
|
206
|
-
@compressor = COMPRESSOR_REGISTRY.lookup(@store_as).new(buffer_type: buffer_type, log: log)
|
|
211
|
+
@compressor = COMPRESSOR_REGISTRY.lookup(@store_as).new(buffer_type: buffer_type, buffer_compressed_type: @buffer.compress, log: log)
|
|
207
212
|
rescue => e
|
|
208
213
|
log.warn "'#{@store_as}' not supported. Use 'text' instead: error = #{e.message}"
|
|
209
214
|
@compressor = TextCompressor.new
|
|
@@ -542,6 +547,10 @@ module Fluent::Plugin
|
|
|
542
547
|
when @aws_key_id && @aws_sec_key
|
|
543
548
|
options[:access_key_id] = @aws_key_id
|
|
544
549
|
options[:secret_access_key] = @aws_sec_key
|
|
550
|
+
when @aws_profile
|
|
551
|
+
options[:profile] = @aws_profile
|
|
552
|
+
when @aws_credential_process
|
|
553
|
+
options[:credentials] = Aws::ProcessCredentials.new(Shellwords.split(@aws_credential_process))
|
|
545
554
|
when @web_identity_credentials
|
|
546
555
|
c = @web_identity_credentials
|
|
547
556
|
region = c.sts_region || @s3_region
|
|
@@ -600,6 +609,7 @@ module Fluent::Plugin
|
|
|
600
609
|
def initialize(opts = {})
|
|
601
610
|
super()
|
|
602
611
|
@buffer_type = opts[:buffer_type]
|
|
612
|
+
@buffer_compressed_type = opts[:buffer_compressed_type]
|
|
603
613
|
@log = opts[:log]
|
|
604
614
|
end
|
|
605
615
|
|
|
@@ -642,11 +652,18 @@ module Fluent::Plugin
|
|
|
642
652
|
end
|
|
643
653
|
|
|
644
654
|
def compress(chunk, tmp)
|
|
655
|
+
if @buffer_compressed_type == :gzip
|
|
656
|
+
chunk.write_to(tmp, compressed: @buffer_compressed_type)
|
|
657
|
+
return
|
|
658
|
+
end
|
|
659
|
+
|
|
645
660
|
w = Zlib::GzipWriter.new(tmp)
|
|
646
661
|
chunk.write_to(w)
|
|
647
662
|
w.finish
|
|
648
663
|
ensure
|
|
649
|
-
w
|
|
664
|
+
if w
|
|
665
|
+
w.finish rescue nil
|
|
666
|
+
end
|
|
650
667
|
end
|
|
651
668
|
end
|
|
652
669
|
|
|
@@ -19,6 +19,11 @@ module Fluent::Plugin
|
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
def compress(chunk, tmp)
|
|
22
|
+
if @buffer_compressed_type == :gzip
|
|
23
|
+
chunk.write_to(tmp, compressed: @buffer_compressed_type)
|
|
24
|
+
return
|
|
25
|
+
end
|
|
26
|
+
|
|
22
27
|
chunk_is_file = @buffer_type == 'file'
|
|
23
28
|
path = if chunk_is_file
|
|
24
29
|
chunk.path
|
|
@@ -25,6 +25,11 @@ module Fluent::Plugin
|
|
|
25
25
|
end
|
|
26
26
|
|
|
27
27
|
def compress(chunk, tmp)
|
|
28
|
+
if @buffer_compressed_type == :zstd
|
|
29
|
+
chunk.write_to(tmp, compressed: @buffer_compressed_type)
|
|
30
|
+
return
|
|
31
|
+
end
|
|
32
|
+
|
|
28
33
|
compressed = Zstd.compress(chunk.read, level: @compress_config.level)
|
|
29
34
|
tmp.write(compressed)
|
|
30
35
|
rescue => e
|
|
@@ -19,26 +19,15 @@ module Fluent::Plugin
|
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
def extract(io)
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
stdout, succeeded = Open3.capture2("gzip #{@command_parameter} #{path}")
|
|
32
|
-
if succeeded
|
|
33
|
-
stdout
|
|
34
|
-
else
|
|
35
|
-
log.warn "failed to execute gzip command. Fallback to GzipReader. status = #{succeeded}"
|
|
36
|
-
begin
|
|
37
|
-
io.rewind
|
|
38
|
-
Zlib::GzipReader.wrap(io) do |gz|
|
|
39
|
-
gz.read
|
|
40
|
-
end
|
|
41
|
-
end
|
|
22
|
+
begin
|
|
23
|
+
extract_with_command("gzip #{@command_parameter}", io, "gzip-temp")
|
|
24
|
+
rescue SizeLimitError
|
|
25
|
+
raise
|
|
26
|
+
rescue => e
|
|
27
|
+
log.warn "gzip command execution failed: #{e.message}. Fallback to GzipExtractor."
|
|
28
|
+
io.rewind
|
|
29
|
+
extractor = GzipExtractor.new(log: log, decompression_size_limit: @decompression_size_limit)
|
|
30
|
+
extractor.extract(io)
|
|
42
31
|
end
|
|
43
32
|
end
|
|
44
33
|
end
|
|
@@ -19,19 +19,11 @@ module Fluent::Plugin
|
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
def extract(io)
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
temp.close
|
|
28
|
-
temp.path
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
stdout, succeeded = Open3.capture2("xz #{@command_parameter} #{path}")
|
|
32
|
-
if succeeded
|
|
33
|
-
stdout
|
|
34
|
-
else
|
|
22
|
+
begin
|
|
23
|
+
extract_with_command("xz #{@command_parameter}", io, "xz-temp")
|
|
24
|
+
rescue SizeLimitError
|
|
25
|
+
raise
|
|
26
|
+
rescue
|
|
35
27
|
raise "Failed to extract #{path} with xz command."
|
|
36
28
|
end
|
|
37
29
|
end
|
|
@@ -19,19 +19,11 @@ module Fluent::Plugin
|
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
def extract(io)
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
temp.close
|
|
28
|
-
temp.path
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
stdout, succeeded = Open3.capture2("lzop #{@command_parameter} #{path}")
|
|
32
|
-
if succeeded
|
|
33
|
-
stdout
|
|
34
|
-
else
|
|
22
|
+
begin
|
|
23
|
+
extract_with_command("lzop #{@command_parameter}", io, "lzop-temp")
|
|
24
|
+
rescue SizeLimitError
|
|
25
|
+
raise
|
|
26
|
+
rescue
|
|
35
27
|
raise "Failed to extract #{path} with lzop command."
|
|
36
28
|
end
|
|
37
29
|
end
|
data/test/test_in_s3.rb
CHANGED
|
@@ -71,6 +71,32 @@ class S3InputTest < Test::Unit::TestCase
|
|
|
71
71
|
end
|
|
72
72
|
end
|
|
73
73
|
|
|
74
|
+
def test_aws_profile
|
|
75
|
+
d = create_driver(%[
|
|
76
|
+
aws_profile test_profile
|
|
77
|
+
s3_bucket test_bucket
|
|
78
|
+
buffer_type memory
|
|
79
|
+
<sqs>
|
|
80
|
+
queue_name test_queue
|
|
81
|
+
queue_owner_aws_account_id 123456789123
|
|
82
|
+
</sqs>
|
|
83
|
+
])
|
|
84
|
+
assert_equal 'test_profile', d.instance.aws_profile
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def test_aws_credential_process
|
|
88
|
+
d = create_driver(%[
|
|
89
|
+
aws_credential_process "/path/to/aws_signing_helper credential-process"
|
|
90
|
+
s3_bucket test_bucket
|
|
91
|
+
buffer_type memory
|
|
92
|
+
<sqs>
|
|
93
|
+
queue_name test_queue
|
|
94
|
+
queue_owner_aws_account_id 123456789123
|
|
95
|
+
</sqs>
|
|
96
|
+
])
|
|
97
|
+
assert_equal '/path/to/aws_signing_helper credential-process', d.instance.aws_credential_process
|
|
98
|
+
end
|
|
99
|
+
|
|
74
100
|
def test_without_sqs_section
|
|
75
101
|
conf = %[
|
|
76
102
|
aws_key_id test_key_id
|
|
@@ -315,7 +341,7 @@ EOS
|
|
|
315
341
|
}
|
|
316
342
|
]
|
|
317
343
|
}
|
|
318
|
-
message = Struct::StubMessage.new(1, 1,
|
|
344
|
+
message = Struct::StubMessage.new(1, 1, JSON.generate(body))
|
|
319
345
|
@sqs_poller.get_messages(anything, anything) do |config, stats|
|
|
320
346
|
config.before_request.call(stats) if config.before_request
|
|
321
347
|
stats.request_count += 1
|
|
@@ -350,7 +376,7 @@ EOS
|
|
|
350
376
|
}
|
|
351
377
|
]
|
|
352
378
|
}
|
|
353
|
-
message = Struct::StubMessage.new(1, 1,
|
|
379
|
+
message = Struct::StubMessage.new(1, 1, JSON.generate(body))
|
|
354
380
|
@sqs_poller.get_messages(anything, anything) do |config, stats|
|
|
355
381
|
config.before_request.call(stats) if config.before_request
|
|
356
382
|
stats.request_count += 1
|
|
@@ -385,7 +411,7 @@ EOS
|
|
|
385
411
|
}
|
|
386
412
|
]
|
|
387
413
|
}
|
|
388
|
-
message = Struct::StubMessage.new(1, 1,
|
|
414
|
+
message = Struct::StubMessage.new(1, 1, JSON.generate(body))
|
|
389
415
|
@sqs_poller.get_messages(anything, anything) do |config, stats|
|
|
390
416
|
config.before_request.call(stats) if config.before_request
|
|
391
417
|
stats.request_count += 1
|
|
@@ -420,7 +446,7 @@ EOS
|
|
|
420
446
|
}
|
|
421
447
|
]
|
|
422
448
|
}
|
|
423
|
-
message = Struct::StubMessage.new(1, 1,
|
|
449
|
+
message = Struct::StubMessage.new(1, 1, JSON.generate(body))
|
|
424
450
|
@sqs_poller.get_messages(anything, anything) do |config, stats|
|
|
425
451
|
config.before_request.call(stats) if config.before_request
|
|
426
452
|
stats.request_count += 1
|
|
@@ -455,7 +481,7 @@ EOS
|
|
|
455
481
|
}
|
|
456
482
|
]
|
|
457
483
|
}
|
|
458
|
-
message = Struct::StubMessage.new(1, 1,
|
|
484
|
+
message = Struct::StubMessage.new(1, 1, JSON.generate(body))
|
|
459
485
|
@sqs_poller.get_messages(anything, anything) do |config, stats|
|
|
460
486
|
config.before_request.call(stats) if config.before_request
|
|
461
487
|
stats.request_count += 1
|
|
@@ -495,7 +521,7 @@ EOS
|
|
|
495
521
|
}
|
|
496
522
|
]
|
|
497
523
|
}
|
|
498
|
-
message = Struct::StubMessage.new(1, 1,
|
|
524
|
+
message = Struct::StubMessage.new(1, 1, JSON.generate(body))
|
|
499
525
|
@sqs_poller.get_messages(anything, anything) do |config, stats|
|
|
500
526
|
config.before_request.call(stats) if config.before_request
|
|
501
527
|
stats.request_count += 1
|
|
@@ -543,7 +569,7 @@ EOS
|
|
|
543
569
|
}
|
|
544
570
|
]
|
|
545
571
|
}
|
|
546
|
-
message = Struct::StubMessage.new(1, 1,
|
|
572
|
+
message = Struct::StubMessage.new(1, 1, JSON.generate(body))
|
|
547
573
|
@sqs_poller.get_messages(anything, anything) do |config, stats|
|
|
548
574
|
config.before_request.call(stats) if config.before_request
|
|
549
575
|
stats.request_count += 1
|
|
@@ -594,7 +620,7 @@ EOS
|
|
|
594
620
|
}
|
|
595
621
|
]
|
|
596
622
|
}
|
|
597
|
-
message = Struct::StubMessage.new(1, 1,
|
|
623
|
+
message = Struct::StubMessage.new(1, 1, JSON.generate(body))
|
|
598
624
|
@sqs_poller.get_messages(anything, anything) do |config, stats|
|
|
599
625
|
config.before_request.call(stats) if config.before_request
|
|
600
626
|
stats.request_count += 1
|
|
@@ -614,6 +640,91 @@ EOS
|
|
|
614
640
|
assert_equal(expected_records, events.map {|_tag, _time, record| record })
|
|
615
641
|
end
|
|
616
642
|
|
|
643
|
+
data(
|
|
644
|
+
"limit_gzip" => { type: "gzip", input: "StringIO", limit: 10, expected_error: true },
|
|
645
|
+
"limit_text" => { type: "text", input: "StringIO", limit: 10, expected_error: true },
|
|
646
|
+
"limit_gzip_command1" => { type: "gzip_command", input: "StringIO", limit: 10, expected_error: true },
|
|
647
|
+
"limit_gzip_command2" => { type: "gzip_command", input: "Tempfile", limit: 10, expected_error: true },
|
|
648
|
+
"normal_gzip_command" => { type: "gzip_command", input: "Tempfile", limit: 100, expected_error: false },
|
|
649
|
+
)
|
|
650
|
+
def test_decompression_size_limit(data)
|
|
651
|
+
store_type = data[:type]
|
|
652
|
+
input_type = data[:input]
|
|
653
|
+
limit = data[:limit]
|
|
654
|
+
setup_mocks
|
|
655
|
+
|
|
656
|
+
config = <<~CONF
|
|
657
|
+
#{CONFIG}
|
|
658
|
+
check_apikey_on_start false
|
|
659
|
+
store_as #{store_type}
|
|
660
|
+
format none
|
|
661
|
+
decompression_size_limit #{limit}
|
|
662
|
+
CONF
|
|
663
|
+
|
|
664
|
+
d = create_driver(config)
|
|
665
|
+
|
|
666
|
+
s3_object = stub(Object.new)
|
|
667
|
+
s3_response = stub(Object.new)
|
|
668
|
+
s3_response.body {
|
|
669
|
+
content = "#{'a'*10}\n#{'b'*10}\n"
|
|
670
|
+
|
|
671
|
+
# Switching between Tempfile and StringIO to cover both branches of the
|
|
672
|
+
# `io.respond_to?(:path)` condition in `extract_with_command`.
|
|
673
|
+
# This ensures that:
|
|
674
|
+
# 1. The StringIO route correctly uses TextExtractor to create a protected temporary file.
|
|
675
|
+
# 2. The Tempfile route correctly limits the output size during Open3.popen3 execution.
|
|
676
|
+
io = (input_type == "Tempfile") ? Tempfile.new : StringIO.new
|
|
677
|
+
|
|
678
|
+
case store_type
|
|
679
|
+
when "gzip", "gzip_command"
|
|
680
|
+
io.binmode
|
|
681
|
+
Zlib::GzipWriter.wrap(io) { |gz|
|
|
682
|
+
gz.write content
|
|
683
|
+
gz.finish
|
|
684
|
+
}
|
|
685
|
+
when "text"
|
|
686
|
+
io.write content
|
|
687
|
+
end
|
|
688
|
+
|
|
689
|
+
io.rewind
|
|
690
|
+
io
|
|
691
|
+
}
|
|
692
|
+
s3_object.get { s3_response }
|
|
693
|
+
@s3_bucket.object(anything).at_least(1) { s3_object }
|
|
694
|
+
|
|
695
|
+
body = {
|
|
696
|
+
"Records" => [
|
|
697
|
+
{
|
|
698
|
+
"s3" => {
|
|
699
|
+
"object" => {
|
|
700
|
+
"key" => "test_key"
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
]
|
|
705
|
+
}
|
|
706
|
+
message = Struct::StubMessage.new(1, 1, Yajl.dump(body))
|
|
707
|
+
@sqs_poller.get_messages(anything, anything) do |config, stats|
|
|
708
|
+
config.before_request.call(stats) if config.before_request
|
|
709
|
+
stats.request_count += 1
|
|
710
|
+
if stats.request_count >= 1
|
|
711
|
+
d.instance.instance_variable_set(:@running, false)
|
|
712
|
+
end
|
|
713
|
+
[message]
|
|
714
|
+
end
|
|
715
|
+
d.run
|
|
716
|
+
|
|
717
|
+
if data[:expected_error]
|
|
718
|
+
# Verify the protection mechanism: ensure SizeLimitError is logged.
|
|
719
|
+
assert_true d.logs.any? { |l| l.include?("Extracted data exceeds limit of #{limit} bytes") }
|
|
720
|
+
else
|
|
721
|
+
# Verify the normal execution path: ensure data is correctly extracted via Open3.popen3.
|
|
722
|
+
expected_records = [{ "message" => "#{'a'*10}\n" }, { "message" => "#{'b'*10}\n" }]
|
|
723
|
+
assert_equal(expected_records, d.events.map {|_tag, _time, record| record })
|
|
724
|
+
assert_false d.logs.any? { |l| l.include?("error_class") }
|
|
725
|
+
end
|
|
726
|
+
end
|
|
727
|
+
|
|
617
728
|
def test_regexp_matching
|
|
618
729
|
setup_mocks
|
|
619
730
|
d = create_driver(CONFIG + "\ncheck_apikey_on_start false\nstore_as text\nformat none\nmatch_regexp .*_key?")
|
|
@@ -635,7 +746,7 @@ EOS
|
|
|
635
746
|
}
|
|
636
747
|
]
|
|
637
748
|
}
|
|
638
|
-
message = Struct::StubMessage.new(1, 1,
|
|
749
|
+
message = Struct::StubMessage.new(1, 1, JSON.generate(body))
|
|
639
750
|
@sqs_poller.get_messages(anything, anything) do |config, stats|
|
|
640
751
|
config.before_request.call(stats) if config.before_request
|
|
641
752
|
stats.request_count += 1
|
|
@@ -664,7 +775,7 @@ EOS
|
|
|
664
775
|
}
|
|
665
776
|
]
|
|
666
777
|
}
|
|
667
|
-
message = Struct::StubMessage.new(1, 1,
|
|
778
|
+
message = Struct::StubMessage.new(1, 1, JSON.generate(body))
|
|
668
779
|
@sqs_poller.get_messages(anything, anything) do |config, stats|
|
|
669
780
|
config.before_request.call(stats) if config.before_request
|
|
670
781
|
stats.request_count += 1
|
|
@@ -708,8 +819,8 @@ EOS
|
|
|
708
819
|
}
|
|
709
820
|
}
|
|
710
821
|
}
|
|
711
|
-
|
|
712
|
-
message = Struct::StubMessage.new(1, 1,
|
|
822
|
+
|
|
823
|
+
message = Struct::StubMessage.new(1, 1, JSON.generate(body))
|
|
713
824
|
@sqs_poller.get_messages(anything, anything) do |config, stats|
|
|
714
825
|
config.before_request.call(stats) if config.before_request
|
|
715
826
|
stats.request_count += 1
|
data/test/test_out_s3.rb
CHANGED
|
@@ -2,6 +2,7 @@ require 'fluent/test'
|
|
|
2
2
|
require 'fluent/test/helpers'
|
|
3
3
|
require 'fluent/test/log'
|
|
4
4
|
require 'fluent/test/driver/output'
|
|
5
|
+
require 'fluent/version'
|
|
5
6
|
require 'aws-sdk-s3'
|
|
6
7
|
require 'fluent/plugin/out_s3'
|
|
7
8
|
|
|
@@ -68,6 +69,30 @@ class S3OutputTest < Test::Unit::TestCase
|
|
|
68
69
|
assert_equal true, d.instance.check_object
|
|
69
70
|
end
|
|
70
71
|
|
|
72
|
+
def test_aws_profile
|
|
73
|
+
d = create_driver(%[
|
|
74
|
+
aws_profile test_profile
|
|
75
|
+
s3_bucket test_bucket
|
|
76
|
+
path log
|
|
77
|
+
utc
|
|
78
|
+
buffer_type memory
|
|
79
|
+
time_slice_format %Y%m%d-%H
|
|
80
|
+
])
|
|
81
|
+
assert_equal 'test_profile', d.instance.aws_profile
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def test_aws_credential_process
|
|
85
|
+
d = create_driver(%[
|
|
86
|
+
aws_credential_process "/path/to/aws_signing_helper credential-process"
|
|
87
|
+
s3_bucket test_bucket
|
|
88
|
+
path log
|
|
89
|
+
utc
|
|
90
|
+
buffer_type memory
|
|
91
|
+
time_slice_format %Y%m%d-%H
|
|
92
|
+
])
|
|
93
|
+
assert_equal '/path/to/aws_signing_helper credential-process', d.instance.aws_credential_process
|
|
94
|
+
end
|
|
95
|
+
|
|
71
96
|
def test_s3_endpoint_with_valid_endpoint
|
|
72
97
|
d = create_driver(CONFIG + 's3_endpoint riak-cs.example.com')
|
|
73
98
|
assert_equal 'riak-cs.example.com', d.instance.s3_endpoint
|
|
@@ -495,6 +520,125 @@ EOC
|
|
|
495
520
|
FileUtils.rm_f(s3_local_file_path)
|
|
496
521
|
end
|
|
497
522
|
|
|
523
|
+
def test_no_compress_by_gzip_with_compressed_buffer
|
|
524
|
+
setup_mocks(true)
|
|
525
|
+
s3_local_file_path = "/tmp/s3-test.gz"
|
|
526
|
+
setup_s3_object_mocks(s3_local_file_path: s3_local_file_path)
|
|
527
|
+
|
|
528
|
+
d = create_time_sliced_driver(CONFIG_TIME_SLICE + <<~EOF)
|
|
529
|
+
<buffer tag,time>
|
|
530
|
+
@type memory
|
|
531
|
+
compress gzip
|
|
532
|
+
timekey 3600
|
|
533
|
+
timekey_use_utc true
|
|
534
|
+
</buffer>
|
|
535
|
+
EOF
|
|
536
|
+
|
|
537
|
+
# GzipCompressor should not use Zlib::GzipWriter
|
|
538
|
+
dont_allow(Zlib::GzipWriter).new
|
|
539
|
+
|
|
540
|
+
time = event_time("2011-01-02 13:14:15 UTC")
|
|
541
|
+
d.run(default_tag: "test") do
|
|
542
|
+
d.feed(time, {"a"=>1})
|
|
543
|
+
d.feed(time, {"a"=>2})
|
|
544
|
+
end
|
|
545
|
+
|
|
546
|
+
data = ""
|
|
547
|
+
File.open(s3_local_file_path, "rb") do |f|
|
|
548
|
+
until f.eof?
|
|
549
|
+
gz = Zlib::GzipReader.new(f)
|
|
550
|
+
data << gz.read
|
|
551
|
+
|
|
552
|
+
unused = gz.unused
|
|
553
|
+
gz.finish
|
|
554
|
+
f.pos -= unused.length if unused
|
|
555
|
+
end
|
|
556
|
+
end
|
|
557
|
+
assert_equal %[2011-01-02T13:14:15Z\ttest\t{"a":1}\n] +
|
|
558
|
+
%[2011-01-02T13:14:15Z\ttest\t{"a":2}\n],
|
|
559
|
+
data
|
|
560
|
+
FileUtils.rm_f(s3_local_file_path)
|
|
561
|
+
end
|
|
562
|
+
|
|
563
|
+
def test_no_compress_by_gzip_command_with_compressed_buffer
|
|
564
|
+
setup_mocks(true)
|
|
565
|
+
s3_local_file_path = "/tmp/s3-test.gz"
|
|
566
|
+
setup_s3_object_mocks(s3_local_file_path: s3_local_file_path)
|
|
567
|
+
|
|
568
|
+
d = create_time_sliced_driver(CONFIG_TIME_SLICE + <<~EOF)
|
|
569
|
+
store_as gzip_command
|
|
570
|
+
<buffer tag,time>
|
|
571
|
+
@type memory
|
|
572
|
+
compress gzip
|
|
573
|
+
timekey 3600
|
|
574
|
+
timekey_use_utc true
|
|
575
|
+
</buffer>
|
|
576
|
+
EOF
|
|
577
|
+
|
|
578
|
+
# GzipCommandCompressor should not use Kernel.system and Zlib::GzipWriter
|
|
579
|
+
dont_allow(Kernel).system
|
|
580
|
+
dont_allow(Zlib::GzipWriter).new
|
|
581
|
+
|
|
582
|
+
time = event_time("2011-01-02 13:14:15 UTC")
|
|
583
|
+
d.run(default_tag: "test") do
|
|
584
|
+
d.feed(time, {"a"=>1})
|
|
585
|
+
d.feed(time, {"a"=>2})
|
|
586
|
+
end
|
|
587
|
+
|
|
588
|
+
data = ""
|
|
589
|
+
File.open(s3_local_file_path, "rb") do |f|
|
|
590
|
+
until f.eof?
|
|
591
|
+
gz = Zlib::GzipReader.new(f)
|
|
592
|
+
data << gz.read
|
|
593
|
+
|
|
594
|
+
unused = gz.unused
|
|
595
|
+
gz.finish
|
|
596
|
+
f.pos -= unused.length if unused
|
|
597
|
+
end
|
|
598
|
+
end
|
|
599
|
+
assert_equal %[2011-01-02T13:14:15Z\ttest\t{"a":1}\n] +
|
|
600
|
+
%[2011-01-02T13:14:15Z\ttest\t{"a":2}\n],
|
|
601
|
+
data
|
|
602
|
+
FileUtils.rm_f(s3_local_file_path)
|
|
603
|
+
end
|
|
604
|
+
|
|
605
|
+
def test_no_compress_by_zstd_with_compressed_buffer
|
|
606
|
+
pend "This test require Fluentd 1.19.0 or later which supports zstd buffer compressed." if Gem::Version.new(Fluent::VERSION) < Gem::Version.new('1.19.0')
|
|
607
|
+
|
|
608
|
+
setup_mocks(true)
|
|
609
|
+
s3_local_file_path = "/tmp/s3-test.zst"
|
|
610
|
+
expected_s3path = "log/events/ts=20110102-13/events_0-#{Socket.gethostname}.zst"
|
|
611
|
+
setup_s3_object_mocks(s3_local_file_path: s3_local_file_path, s3path: expected_s3path)
|
|
612
|
+
|
|
613
|
+
d = create_time_sliced_driver(CONFIG_TIME_SLICE + <<~EOF)
|
|
614
|
+
store_as zstd
|
|
615
|
+
<buffer tag,time>
|
|
616
|
+
@type memory
|
|
617
|
+
compress zstd
|
|
618
|
+
timekey 3600
|
|
619
|
+
timekey_use_utc true
|
|
620
|
+
</buffer>
|
|
621
|
+
EOF
|
|
622
|
+
|
|
623
|
+
# ZstdCompressor should not use Zstd.compress
|
|
624
|
+
dont_allow(Zstd).compress
|
|
625
|
+
|
|
626
|
+
time = event_time("2011-01-02 13:14:15 UTC")
|
|
627
|
+
d.run(default_tag: "test") do
|
|
628
|
+
d.feed(time, {"a"=>1})
|
|
629
|
+
d.feed(time, {"a"=>2})
|
|
630
|
+
end
|
|
631
|
+
|
|
632
|
+
File.open(s3_local_file_path, 'rb') do |file|
|
|
633
|
+
compressed_data = file.read
|
|
634
|
+
uncompressed_data = Zstd.decompress(compressed_data)
|
|
635
|
+
expected_data = %[2011-01-02T13:14:15Z\ttest\t{"a":1}\n] +
|
|
636
|
+
%[2011-01-02T13:14:15Z\ttest\t{"a":2}\n]
|
|
637
|
+
assert_equal expected_data, uncompressed_data
|
|
638
|
+
end
|
|
639
|
+
FileUtils.rm_f(s3_local_file_path)
|
|
640
|
+
end
|
|
641
|
+
|
|
498
642
|
class MockResponse
|
|
499
643
|
attr_reader :data
|
|
500
644
|
|
metadata
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: fluent-plugin-s3
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.8.
|
|
4
|
+
version: 1.8.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sadayuki Furuhashi
|
|
8
8
|
- Masahiro Nakagawa
|
|
9
|
-
autorequire:
|
|
10
9
|
bindir: bin
|
|
11
10
|
cert_chain: []
|
|
12
|
-
date:
|
|
11
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
13
12
|
dependencies:
|
|
14
13
|
- !ruby/object:Gem::Dependency
|
|
15
14
|
name: fluentd
|
|
@@ -116,7 +115,7 @@ dependencies:
|
|
|
116
115
|
- !ruby/object:Gem::Version
|
|
117
116
|
version: '0'
|
|
118
117
|
- !ruby/object:Gem::Dependency
|
|
119
|
-
name:
|
|
118
|
+
name: ostruct
|
|
120
119
|
requirement: !ruby/object:Gem::Requirement
|
|
121
120
|
requirements:
|
|
122
121
|
- - ">="
|
|
@@ -130,7 +129,7 @@ dependencies:
|
|
|
130
129
|
- !ruby/object:Gem::Version
|
|
131
130
|
version: '0'
|
|
132
131
|
- !ruby/object:Gem::Dependency
|
|
133
|
-
name:
|
|
132
|
+
name: rexml
|
|
134
133
|
requirement: !ruby/object:Gem::Requirement
|
|
135
134
|
requirements:
|
|
136
135
|
- - ">="
|
|
@@ -143,6 +142,20 @@ dependencies:
|
|
|
143
142
|
- - ">="
|
|
144
143
|
- !ruby/object:Gem::Version
|
|
145
144
|
version: '0'
|
|
145
|
+
- !ruby/object:Gem::Dependency
|
|
146
|
+
name: zstd-ruby
|
|
147
|
+
requirement: !ruby/object:Gem::Requirement
|
|
148
|
+
requirements:
|
|
149
|
+
- - "~>"
|
|
150
|
+
- !ruby/object:Gem::Version
|
|
151
|
+
version: '1.5'
|
|
152
|
+
type: :development
|
|
153
|
+
prerelease: false
|
|
154
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
155
|
+
requirements:
|
|
156
|
+
- - "~>"
|
|
157
|
+
- !ruby/object:Gem::Version
|
|
158
|
+
version: '1.5'
|
|
146
159
|
description: Amazon S3 output plugin for Fluentd event collector
|
|
147
160
|
email: frsyuki@gmail.com
|
|
148
161
|
executables: []
|
|
@@ -187,7 +200,6 @@ homepage: https://github.com/fluent/fluent-plugin-s3
|
|
|
187
200
|
licenses:
|
|
188
201
|
- Apache-2.0
|
|
189
202
|
metadata: {}
|
|
190
|
-
post_install_message:
|
|
191
203
|
rdoc_options: []
|
|
192
204
|
require_paths:
|
|
193
205
|
- lib
|
|
@@ -202,8 +214,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
202
214
|
- !ruby/object:Gem::Version
|
|
203
215
|
version: '0'
|
|
204
216
|
requirements: []
|
|
205
|
-
rubygems_version:
|
|
206
|
-
signing_key:
|
|
217
|
+
rubygems_version: 4.0.10
|
|
207
218
|
specification_version: 4
|
|
208
219
|
summary: Amazon S3 output plugin for Fluentd event collector
|
|
209
|
-
test_files:
|
|
220
|
+
test_files:
|
|
221
|
+
- test/test_in_s3.rb
|
|
222
|
+
- test/test_out_s3.rb
|