fluent-plugin-s3 1.7.2 → 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/ISSUE_TEMPLATE/bug_report.yaml +1 -0
- data/.github/ISSUE_TEMPLATE/feature_request.yaml +1 -0
- data/.github/dependabot.yml +23 -0
- data/.github/workflows/linux.yml +13 -4
- data/.github/workflows/stale-actions.yml +14 -9
- data/ChangeLog +30 -0
- data/README.md +1 -2
- data/VERSION +1 -1
- data/docs/credentials.md +18 -1
- data/docs/input.md +5 -0
- data/docs/output.md +14 -1
- data/fluent-plugin-s3.gemspec +2 -0
- data/lib/fluent/plugin/in_s3.rb +73 -11
- data/lib/fluent/plugin/out_s3.rb +44 -7
- data/lib/fluent/plugin/s3_compressor_gzip_command.rb +5 -0
- data/lib/fluent/plugin/s3_compressor_zstd.rb +41 -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 +269 -0
- metadata +36 -7
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
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
version: 2
|
|
2
|
+
updates:
|
|
3
|
+
- package-ecosystem: 'github-actions'
|
|
4
|
+
directory: '/'
|
|
5
|
+
schedule:
|
|
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 bundler rake
|
|
27
36
|
bundle install --jobs 4 --retry 3
|
|
28
37
|
bundle exec rake test
|
|
@@ -6,17 +6,22 @@ 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
|
-
days-before-stale:
|
|
14
|
-
days-before-close:
|
|
15
|
-
stale-issue-message: "This issue has been automatically marked as stale because it has been open
|
|
16
|
-
stale-pr-message: "This PR has been automatically marked as stale because it has been open
|
|
17
|
-
close-issue-message: "This issue was automatically closed because of stale in
|
|
18
|
-
close-pr-message: "This PR was automatically closed because of stale in
|
|
16
|
+
days-before-stale: 30
|
|
17
|
+
days-before-close: 7
|
|
18
|
+
stale-issue-message: "This issue has been automatically marked as stale because it has been open 30 days with no activity. Remove stale label or comment or this issue will be closed in 7 days"
|
|
19
|
+
stale-pr-message: "This PR has been automatically marked as stale because it has been open 30 days with no activity. Remove stale label or comment or this PR will be closed in 7 days"
|
|
20
|
+
close-issue-message: "This issue was automatically closed because of stale in 7 days"
|
|
21
|
+
close-pr-message: "This PR was automatically closed because of stale in 7 days"
|
|
19
22
|
stale-pr-label: "stale"
|
|
20
23
|
stale-issue-label: "stale"
|
|
21
|
-
exempt-issue-labels: "bug,help wanted,enhancement"
|
|
22
|
-
exempt-pr-labels: "bug,help wanted,enhancement"
|
|
24
|
+
exempt-issue-labels: "waiting-for-triage,bug,help wanted,enhancement"
|
|
25
|
+
exempt-pr-labels: "waiting-for-triage,bug,help wanted,enhancement"
|
|
26
|
+
exempt-all-assignees: true
|
|
27
|
+
exempt-all-milestones: true
|
data/ChangeLog
CHANGED
|
@@ -1,3 +1,33 @@
|
|
|
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
|
+
|
|
14
|
+
Release 1.8.3 - 2025/02/18
|
|
15
|
+
|
|
16
|
+
* out_s3: Add `sts_http_proxy` and `sts_endpoint_url` to web_identity_credentials (GitHub: #452)
|
|
17
|
+
|
|
18
|
+
Release 1.8.2 - 2024/12/18
|
|
19
|
+
|
|
20
|
+
* out_s3: Add more logging to identify unexpected error of Tempfile#close.
|
|
21
|
+
* out_s3: Support `checksum_algorithm` parameter to validate the data with checksum (CRC32, CRC32C, SHA1 and SHA256) during upload/download.
|
|
22
|
+
|
|
23
|
+
Release 1.8.1 - 2024/11/15
|
|
24
|
+
|
|
25
|
+
* dependency: Change zstd-ruby dependency optional. Install zstd-ruby manually if you want to enable Zstd compression feature.
|
|
26
|
+
|
|
27
|
+
Release 1.8.0 - 2024/11/06
|
|
28
|
+
|
|
29
|
+
* out_s3: Add zstd compression support
|
|
30
|
+
|
|
1
31
|
Release 1.7.2 - 2022/10/19
|
|
2
32
|
|
|
3
33
|
* in_s3: Add `event_bridge_mode` parameter
|
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.
|
|
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.
|
|
@@ -126,7 +143,7 @@ See also:
|
|
|
126
143
|
|
|
127
144
|
### retries
|
|
128
145
|
|
|
129
|
-
Number of times to retry when retrieving credentials. Default is
|
|
146
|
+
Number of times to retry when retrieving credentials. Default is nil.
|
|
130
147
|
|
|
131
148
|
### ip_address
|
|
132
149
|
|
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
|
|
|
@@ -430,6 +431,18 @@ Specifies the AWS KMS key ID to use for object encryption.
|
|
|
430
431
|
|
|
431
432
|
Specifies the 128-bit MD5 digest of the encryption key according to RFC 1321.
|
|
432
433
|
|
|
434
|
+
## checksum_algorithm
|
|
435
|
+
|
|
436
|
+
AWS allows to calculate the integrity checksum server side. The additional checksum is
|
|
437
|
+
used to validate the data during upload or download. The following 4 SHA and CRC algorithms are supported:
|
|
438
|
+
|
|
439
|
+
* CRC32
|
|
440
|
+
* CRC32C
|
|
441
|
+
* SHA1
|
|
442
|
+
* SHA256
|
|
443
|
+
|
|
444
|
+
For more info refer to [object integrity](https://docs.aws.amazon.com/AmazonS3/latest/userguide/checking-object-integrity.html).
|
|
445
|
+
|
|
433
446
|
## compute_checksums
|
|
434
447
|
|
|
435
448
|
AWS SDK uses MD5 for API request/response by default. On FIPS enabled environment,
|
data/fluent-plugin-s3.gemspec
CHANGED
|
@@ -23,7 +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"
|
|
30
|
+
gem.add_development_dependency "zstd-ruby", ["~> 1.5"]
|
|
29
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
|
|
@@ -62,6 +67,10 @@ module Fluent::Plugin
|
|
|
62
67
|
config_param :duration_seconds, :integer, default: nil
|
|
63
68
|
desc "The region of the STS endpoint to use."
|
|
64
69
|
config_param :sts_region, :string, default: nil
|
|
70
|
+
desc "A http proxy url for requests to aws sts service"
|
|
71
|
+
config_param :sts_http_proxy, :string, default: nil, secret: true
|
|
72
|
+
desc "A url for a regional sts api endpoint, the default is global"
|
|
73
|
+
config_param :sts_endpoint_url, :string, default: nil
|
|
65
74
|
end
|
|
66
75
|
config_section :instance_profile_credentials, multi: false do
|
|
67
76
|
desc "Number of times to retry when retrieving credentials"
|
|
@@ -154,6 +163,8 @@ module Fluent::Plugin
|
|
|
154
163
|
desc "Arbitrary S3 tag-set for the object"
|
|
155
164
|
config_param :tagging, :string, default: nil
|
|
156
165
|
desc "Arbitrary S3 metadata headers to set for the object"
|
|
166
|
+
config_param :checksum_algorithm, :string, default: nil
|
|
167
|
+
desc "Indicates the algorithm you want Amazon S3 to use to create the checksum for the object (CRC32,CRC32C,SHA1,SHA256)"
|
|
157
168
|
config_param :s3_metadata, :hash, default: nil
|
|
158
169
|
config_section :bucket_lifecycle_rule, param_name: :bucket_lifecycle_rules, multi: true do
|
|
159
170
|
desc "A unique ID for this rule"
|
|
@@ -197,7 +208,7 @@ module Fluent::Plugin
|
|
|
197
208
|
|
|
198
209
|
begin
|
|
199
210
|
buffer_type = @buffer_config[:@type]
|
|
200
|
-
@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)
|
|
201
212
|
rescue => e
|
|
202
213
|
log.warn "'#{@store_as}' not supported. Use 'text' instead: error = #{e.message}"
|
|
203
214
|
@compressor = TextCompressor.new
|
|
@@ -368,6 +379,7 @@ module Fluent::Plugin
|
|
|
368
379
|
put_options[:grant_read] = @grant_read if @grant_read
|
|
369
380
|
put_options[:grant_read_acp] = @grant_read_acp if @grant_read_acp
|
|
370
381
|
put_options[:grant_write_acp] = @grant_write_acp if @grant_write_acp
|
|
382
|
+
put_options[:checksum_algorithm] = @checksum_algorithm if @checksum_algorithm
|
|
371
383
|
put_options[:tagging] = @tagging if @tagging
|
|
372
384
|
|
|
373
385
|
if @s3_metadata
|
|
@@ -378,6 +390,8 @@ module Fluent::Plugin
|
|
|
378
390
|
end
|
|
379
391
|
@bucket.object(s3path).put(put_options)
|
|
380
392
|
|
|
393
|
+
log.debug "out_s3: completed to write chunk #{dump_unique_id_hex(chunk.unique_id)} with metadata #{chunk.metadata} to s3://#{@s3_bucket}/#{s3path}"
|
|
394
|
+
|
|
381
395
|
@values_for_s3_object_chunk.delete(chunk.unique_id)
|
|
382
396
|
|
|
383
397
|
if @warn_for_delay
|
|
@@ -386,7 +400,11 @@ module Fluent::Plugin
|
|
|
386
400
|
end
|
|
387
401
|
end
|
|
388
402
|
ensure
|
|
389
|
-
|
|
403
|
+
begin
|
|
404
|
+
tmp.close(true)
|
|
405
|
+
rescue => e
|
|
406
|
+
log.info "out_s3: Tempfile#close caused unexpected error", error: e
|
|
407
|
+
end
|
|
390
408
|
end
|
|
391
409
|
end
|
|
392
410
|
|
|
@@ -529,17 +547,28 @@ module Fluent::Plugin
|
|
|
529
547
|
when @aws_key_id && @aws_sec_key
|
|
530
548
|
options[:access_key_id] = @aws_key_id
|
|
531
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))
|
|
532
554
|
when @web_identity_credentials
|
|
533
555
|
c = @web_identity_credentials
|
|
556
|
+
region = c.sts_region || @s3_region
|
|
534
557
|
credentials_options[:role_arn] = c.role_arn
|
|
535
558
|
credentials_options[:role_session_name] = c.role_session_name
|
|
536
559
|
credentials_options[:web_identity_token_file] = c.web_identity_token_file
|
|
537
560
|
credentials_options[:policy] = c.policy if c.policy
|
|
538
561
|
credentials_options[:duration_seconds] = c.duration_seconds if c.duration_seconds
|
|
539
|
-
if c.
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
credentials_options[:client] = Aws::STS::Client.new(:region
|
|
562
|
+
credentials_options[:sts_endpoint_url] = c.sts_endpoint_url if c.sts_endpoint_url
|
|
563
|
+
credentials_options[:sts_http_proxy] = c.sts_http_proxy if c.sts_http_proxy
|
|
564
|
+
if c.sts_http_proxy && c.sts_endpoint_url
|
|
565
|
+
credentials_options[:client] = Aws::STS::Client.new(region: region, http_proxy: c.sts_http_proxy, endpoint: c.sts_endpoint_url)
|
|
566
|
+
elsif c.sts_http_proxy
|
|
567
|
+
credentials_options[:client] = Aws::STS::Client.new(region: region, http_proxy: c.sts_http_proxy)
|
|
568
|
+
elsif c.sts_endpoint_url
|
|
569
|
+
credentials_options[:client] = Aws::STS::Client.new(region: region, endpoint: c.sts_endpoint_url)
|
|
570
|
+
else
|
|
571
|
+
credentials_options[:client] = Aws::STS::Client.new(region: region)
|
|
543
572
|
end
|
|
544
573
|
options[:credentials] = Aws::AssumeRoleWebIdentityCredentials.new(credentials_options)
|
|
545
574
|
when @instance_profile_credentials
|
|
@@ -580,6 +609,7 @@ module Fluent::Plugin
|
|
|
580
609
|
def initialize(opts = {})
|
|
581
610
|
super()
|
|
582
611
|
@buffer_type = opts[:buffer_type]
|
|
612
|
+
@buffer_compressed_type = opts[:buffer_compressed_type]
|
|
583
613
|
@log = opts[:log]
|
|
584
614
|
end
|
|
585
615
|
|
|
@@ -622,11 +652,18 @@ module Fluent::Plugin
|
|
|
622
652
|
end
|
|
623
653
|
|
|
624
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
|
+
|
|
625
660
|
w = Zlib::GzipWriter.new(tmp)
|
|
626
661
|
chunk.write_to(w)
|
|
627
662
|
w.finish
|
|
628
663
|
ensure
|
|
629
|
-
w
|
|
664
|
+
if w
|
|
665
|
+
w.finish rescue nil
|
|
666
|
+
end
|
|
630
667
|
end
|
|
631
668
|
end
|
|
632
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
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
module Fluent::Plugin
|
|
2
|
+
class S3Output
|
|
3
|
+
class ZstdCompressor < Compressor
|
|
4
|
+
S3Output.register_compressor('zstd', self)
|
|
5
|
+
|
|
6
|
+
config_section :compress, param_name: :compress_config, init: true, multi: false do
|
|
7
|
+
desc "Compression level for zstd (1-22)"
|
|
8
|
+
config_param :level, :integer, default: 3
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def initialize(opts = {})
|
|
12
|
+
super
|
|
13
|
+
require 'zstd-ruby'
|
|
14
|
+
rescue LoadError => e
|
|
15
|
+
log.error "failed to load zstd-ruby gem. You need to manually install 'zstd-ruby' gem to use 'zstd'.", error: e.message
|
|
16
|
+
raise Fluent::ConfigError, "failed to load 'zstd-ruby' gem"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def ext
|
|
20
|
+
'zst'.freeze
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def content_type
|
|
24
|
+
'application/x-zst'.freeze
|
|
25
|
+
end
|
|
26
|
+
|
|
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
|
+
|
|
33
|
+
compressed = Zstd.compress(chunk.read, level: @compress_config.level)
|
|
34
|
+
tmp.write(compressed)
|
|
35
|
+
rescue => e
|
|
36
|
+
log.warn "zstd compression failed: #{e.message}"
|
|
37
|
+
raise
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|