fluentd-plugin-kinesis-intuit 2.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.idea/.rakeTasks +7 -0
- data/.idea/aws-fluentd-plugin-kinesis-intuit.iml +55 -0
- data/.idea/misc.xml +7 -0
- data/.idea/modules.xml +8 -0
- data/.idea/vcs.xml +6 -0
- data/.idea/workspace.xml +1029 -0
- data/.travis.yml +14 -0
- data/CHANGELOG.md +115 -0
- data/CODE_OF_CONDUCT.md +4 -0
- data/COMMUNITY.md +26 -0
- data/CONTRIBUTING.md +160 -0
- data/CONTRIBUTORS.txt +6 -0
- data/Gemfile +18 -0
- data/Gemfile.lock +112 -0
- data/LICENSE.txt +201 -0
- data/Makefile +44 -0
- data/NOTICE.txt +2 -0
- data/README.md +62 -0
- data/Rakefile +26 -0
- data/benchmark/task.rake +106 -0
- data/fluent-plugin-kinesis.gemspec +53 -0
- data/gemfiles/Gemfile.fluentd-0.14.10 +20 -0
- data/gemfiles/Gemfile.td-agent-3.2.0 +31 -0
- data/lib/fluent/plugin/kinesis.rb +146 -0
- data/lib/fluent/plugin/kinesis_helper/aggregator.rb +101 -0
- data/lib/fluent/plugin/kinesis_helper/api.rb +198 -0
- data/lib/fluent/plugin/kinesis_helper/client.rb +170 -0
- data/lib/fluent/plugin/out_kinesis_firehose.rb +59 -0
- data/lib/fluent/plugin/out_kinesis_streams.rb +160 -0
- data/lib/fluent/plugin/out_kinesis_streams_aggregated.rb +78 -0
- data/lib/fluent_plugin_kinesis/version.rb +17 -0
- metadata +290 -0
@@ -0,0 +1,170 @@
|
|
1
|
+
#
|
2
|
+
# Copyright 2014-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License"). You
|
5
|
+
# may not use this file except in compliance with the License. A copy of
|
6
|
+
# the License is located at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# or in the "license" file accompanying this file. This file is
|
11
|
+
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
12
|
+
# ANY KIND, either express or implied. See the License for the specific
|
13
|
+
# language governing permissions and limitations under the License.
|
14
|
+
|
15
|
+
require 'fluent/configurable'
|
16
|
+
require 'aws-sdk-core'
|
17
|
+
|
18
|
+
module Fluent
|
19
|
+
module Plugin
|
20
|
+
module KinesisHelper
|
21
|
+
module Client
|
22
|
+
module ClientParams
|
23
|
+
include Fluent::Configurable
|
24
|
+
config_param :region, :string, default: nil
|
25
|
+
|
26
|
+
config_param :http_proxy, :string, default: nil, secret: true
|
27
|
+
config_param :endpoint, :string, default: nil
|
28
|
+
config_param :ssl_verify_peer, :bool, default: true
|
29
|
+
|
30
|
+
config_param :aws_key_id, :string, default: nil, secret: true
|
31
|
+
config_param :aws_sec_key, :string, default: nil, secret: true
|
32
|
+
config_section :assume_role_credentials, multi: false do
|
33
|
+
desc "The Amazon Resource Name (ARN) of the role to assume"
|
34
|
+
config_param :role_arn, :string, secret: true
|
35
|
+
desc "An identifier for the assumed role session"
|
36
|
+
config_param :role_session_name, :string
|
37
|
+
desc "An IAM policy in JSON format"
|
38
|
+
config_param :policy, :string, default: nil
|
39
|
+
desc "The duration, in seconds, of the role session (900-3600)"
|
40
|
+
config_param :duration_seconds, :integer, default: nil
|
41
|
+
desc "A unique identifier that is used by third parties when assuming roles in their customers' accounts."
|
42
|
+
config_param :external_id, :string, default: nil, secret: true
|
43
|
+
desc "A http proxy url for requests to aws sts service"
|
44
|
+
config_param :sts_http_proxy, :string, default: nil, secret: true
|
45
|
+
end
|
46
|
+
config_section :instance_profile_credentials, multi: false do
|
47
|
+
desc "Number of times to retry when retrieving credentials"
|
48
|
+
config_param :retries, :integer, default: nil
|
49
|
+
desc "IP address (default:169.254.169.254)"
|
50
|
+
config_param :ip_address, :string, default: nil
|
51
|
+
desc "Port number (default:80)"
|
52
|
+
config_param :port, :integer, default: nil
|
53
|
+
desc "Number of seconds to wait for the connection to open"
|
54
|
+
config_param :http_open_timeout, :float, default: nil
|
55
|
+
desc "Number of seconds to wait for one block to be read"
|
56
|
+
config_param :http_read_timeout, :float, default: nil
|
57
|
+
# config_param :delay, :integer or :proc, :default => nil
|
58
|
+
# config_param :http_degub_output, :io, :default => nil
|
59
|
+
end
|
60
|
+
config_section :shared_credentials, multi: false do
|
61
|
+
desc "Path to the shared file. (default: $HOME/.aws/credentials)"
|
62
|
+
config_param :path, :string, default: nil
|
63
|
+
desc "Profile name. Default to 'default' or ENV['AWS_PROFILE']"
|
64
|
+
config_param :profile_name, :string, default: nil
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.included(mod)
|
69
|
+
mod.include ClientParams
|
70
|
+
end
|
71
|
+
|
72
|
+
def configure(conf)
|
73
|
+
super
|
74
|
+
@region = client.config.region if @region.nil?
|
75
|
+
end
|
76
|
+
|
77
|
+
def client
|
78
|
+
@client ||= client_class.new(client_options)
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
def aws_sdk_v2?
|
84
|
+
@aws_sdk_v2 ||= Gem.loaded_specs['aws-sdk-core'].version < Gem::Version.create('3')
|
85
|
+
end
|
86
|
+
|
87
|
+
def client_class
|
88
|
+
case request_type
|
89
|
+
when :streams, :streams_aggregated
|
90
|
+
if aws_sdk_v2?
|
91
|
+
require 'aws-sdk'
|
92
|
+
else
|
93
|
+
require 'aws-sdk-kinesis'
|
94
|
+
end
|
95
|
+
Aws::Kinesis::Client
|
96
|
+
when :firehose
|
97
|
+
if aws_sdk_v2?
|
98
|
+
require 'aws-sdk'
|
99
|
+
else
|
100
|
+
require 'aws-sdk-firehose'
|
101
|
+
end
|
102
|
+
Aws::Firehose::Client
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def client_options
|
107
|
+
options = setup_credentials
|
108
|
+
options.update(
|
109
|
+
user_agent_suffix: "fluent-plugin-kinesis/#{request_type}/#{FluentPluginKinesis::VERSION}"
|
110
|
+
)
|
111
|
+
options.update(region: @region) unless @region.nil?
|
112
|
+
options.update(http_proxy: @http_proxy) unless @http_proxy.nil?
|
113
|
+
options.update(endpoint: @endpoint) unless @endpoint.nil?
|
114
|
+
options.update(ssl_verify_peer: @ssl_verify_peer) unless @ssl_verify_peer.nil?
|
115
|
+
if @debug
|
116
|
+
options.update(logger: Logger.new(log.out))
|
117
|
+
options.update(log_level: :debug)
|
118
|
+
end
|
119
|
+
options
|
120
|
+
end
|
121
|
+
|
122
|
+
def setup_credentials
|
123
|
+
options = {}
|
124
|
+
credentials_options = {}
|
125
|
+
case
|
126
|
+
when @aws_key_id && @aws_sec_key
|
127
|
+
options[:access_key_id] = @aws_key_id
|
128
|
+
options[:secret_access_key] = @aws_sec_key
|
129
|
+
when @assume_role_credentials
|
130
|
+
c = @assume_role_credentials
|
131
|
+
credentials_options[:role_arn] = c.role_arn
|
132
|
+
credentials_options[:role_session_name] = c.role_session_name
|
133
|
+
credentials_options[:policy] = c.policy if c.policy
|
134
|
+
credentials_options[:duration_seconds] = c.duration_seconds if c.duration_seconds
|
135
|
+
credentials_options[:external_id] = c.external_id if c.external_id
|
136
|
+
if c.sts_http_proxy and @region
|
137
|
+
credentials_options[:client] = Aws::STS::Client.new(region: @region, http_proxy: c.sts_http_proxy)
|
138
|
+
elsif @region
|
139
|
+
credentials_options[:client] = Aws::STS::Client.new(region: @region)
|
140
|
+
elsif c.sts_http_proxy
|
141
|
+
credentials_options[:client] = Aws::STS::Client.new(http_proxy: c.sts_http_proxy)
|
142
|
+
end
|
143
|
+
options[:credentials] = Aws::AssumeRoleCredentials.new(credentials_options)
|
144
|
+
when @instance_profile_credentials
|
145
|
+
c = @instance_profile_credentials
|
146
|
+
credentials_options[:retries] = c.retries if c.retries
|
147
|
+
credentials_options[:ip_address] = c.ip_address if c.ip_address
|
148
|
+
credentials_options[:port] = c.port if c.port
|
149
|
+
credentials_options[:http_open_timeout] = c.http_open_timeout if c.http_open_timeout
|
150
|
+
credentials_options[:http_read_timeout] = c.http_read_timeout if c.http_read_timeout
|
151
|
+
if ENV["AWS_CONTAINER_CREDENTIALS_RELATIVE_URI"]
|
152
|
+
options[:credentials] = Aws::ECSCredentials.new(credentials_options)
|
153
|
+
else
|
154
|
+
options[:credentials] = Aws::InstanceProfileCredentials.new(credentials_options)
|
155
|
+
end
|
156
|
+
when @shared_credentials
|
157
|
+
c = @shared_credentials
|
158
|
+
credentials_options[:path] = c.path if c.path
|
159
|
+
credentials_options[:profile_name] = c.profile_name if c.profile_name
|
160
|
+
options[:credentials] = Aws::SharedCredentials.new(credentials_options)
|
161
|
+
else
|
162
|
+
# Use default credentials
|
163
|
+
# See http://docs.aws.amazon.com/sdkforruby/api/Aws/S3/Client.html
|
164
|
+
end
|
165
|
+
options
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
#
|
2
|
+
# Copyright 2014-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License"). You
|
5
|
+
# may not use this file except in compliance with the License. A copy of
|
6
|
+
# the License is located at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# or in the "license" file accompanying this file. This file is
|
11
|
+
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
12
|
+
# ANY KIND, either express or implied. See the License for the specific
|
13
|
+
# language governing permissions and limitations under the License.
|
14
|
+
|
15
|
+
require 'fluent/plugin/kinesis'
|
16
|
+
|
17
|
+
module Fluent
|
18
|
+
module Plugin
|
19
|
+
class KinesisFirehoseOutput < KinesisOutput
|
20
|
+
Fluent::Plugin.register_output('kinesis_firehose', self)
|
21
|
+
|
22
|
+
RequestType = :firehose
|
23
|
+
BatchRequestLimitCount = 500
|
24
|
+
BatchRequestLimitSize = 4 * 1024 * 1024
|
25
|
+
include KinesisHelper::API::BatchRequest
|
26
|
+
|
27
|
+
config_param :delivery_stream_name, :string
|
28
|
+
config_param :append_new_line, :bool, default: true
|
29
|
+
|
30
|
+
def configure(conf)
|
31
|
+
super
|
32
|
+
if @append_new_line
|
33
|
+
org_data_formatter = @data_formatter
|
34
|
+
@data_formatter = ->(tag, time, record) {
|
35
|
+
org_data_formatter.call(tag, time, record) + "\n"
|
36
|
+
}
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def format(tag, time, record)
|
41
|
+
format_for_api do
|
42
|
+
[@data_formatter.call(tag, time, record)]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def write(chunk)
|
47
|
+
write_records_batch(chunk) do |batch|
|
48
|
+
records = batch.map{|(data)|
|
49
|
+
{ data: data }
|
50
|
+
}
|
51
|
+
client.put_record_batch(
|
52
|
+
delivery_stream_name: @delivery_stream_name,
|
53
|
+
records: records,
|
54
|
+
)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,160 @@
|
|
1
|
+
#
|
2
|
+
# Copyright 2014-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License"). You
|
5
|
+
# may not use this file except in compliance with the License. A copy of
|
6
|
+
# the License is located at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# or in the "license" file accompanying this file. This file is
|
11
|
+
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
12
|
+
# ANY KIND, either express or implied. See the License for the specific
|
13
|
+
# language governing permissions and limitations under the License.
|
14
|
+
|
15
|
+
require 'fluent/plugin/kinesis'
|
16
|
+
|
17
|
+
module Fluent
|
18
|
+
module Plugin
|
19
|
+
class KinesisStreamsOutput < KinesisOutput
|
20
|
+
Fluent::Plugin.register_output('kinesis_streams', self)
|
21
|
+
|
22
|
+
RequestType = :streams
|
23
|
+
BatchRequestLimitCount = 500
|
24
|
+
BatchRequestLimitSize = 5 * 1024 * 1024
|
25
|
+
include KinesisHelper::API::BatchRequest
|
26
|
+
|
27
|
+
config_param :stream_name, :string
|
28
|
+
config_param :partition_key, :string, default: nil
|
29
|
+
|
30
|
+
desc 'The Splunk index to index events. When not set, will be decided by HEC'
|
31
|
+
config_param :index, :string, default: nil
|
32
|
+
|
33
|
+
desc 'The host field for events, by default it uses the hostname of the machine that runnning fluentd. '
|
34
|
+
config_param :host, :string, default: nil
|
35
|
+
|
36
|
+
desc 'The source field for events, when not set, will be decided by HEC.'
|
37
|
+
config_param :source, :string, default: nil
|
38
|
+
|
39
|
+
desc 'The sourcetype field for events, when not set, will be decided by HEC.'
|
40
|
+
config_param :sourcetype, :string, default: nil
|
41
|
+
|
42
|
+
desc 'All items in the record will be sent if true, otherwise only message'
|
43
|
+
config_param :all_items, :bool, :default => false
|
44
|
+
|
45
|
+
def initialize
|
46
|
+
super
|
47
|
+
@default_host = Socket.gethostname
|
48
|
+
end
|
49
|
+
|
50
|
+
# Thanks to
|
51
|
+
# https://github.com/kazegusuri/fluent-plugin-prometheus/blob/348c112d/lib/fluent/plugin/prometheus.rb
|
52
|
+
def self.placeholder_expander(log)
|
53
|
+
# Use internal class in order to expand placeholder
|
54
|
+
if defined?(Fluent::Filter) # for v0.12, built-in PlaceholderExpander
|
55
|
+
begin
|
56
|
+
require 'fluent/plugin/filter_record_transformer'
|
57
|
+
if defined?(Fluent::Plugin::RecordTransformerFilter::PlaceholderExpander)
|
58
|
+
# for v0.14
|
59
|
+
return Fluent::Plugin::RecordTransformerFilter::PlaceholderExpander.new(log: log)
|
60
|
+
else
|
61
|
+
# for v0.12
|
62
|
+
return Fluent::RecordTransformerFilter::PlaceholderExpander.new(log: log)
|
63
|
+
end
|
64
|
+
rescue LoadError => e
|
65
|
+
raise ConfigError, "cannot find filter_record_transformer plugin: #{e.message}"
|
66
|
+
end
|
67
|
+
else # for v0.10, use PlaceholderExapander in fluent-plugin-record-reformer plugin
|
68
|
+
begin
|
69
|
+
require 'fluent/plugin/out_record_reformer.rb'
|
70
|
+
return Fluent::RecordReformerOutput::PlaceholderExpander.new(log: log)
|
71
|
+
rescue LoadError => e
|
72
|
+
raise ConfigError, "cannot find fluent-plugin-record-reformer: #{e.message}"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def configure(conf)
|
78
|
+
super
|
79
|
+
# check_conflict
|
80
|
+
# prepare_key_fields
|
81
|
+
@placeholder_expander = Fluent::Plugin::KinesisStreamsOutput.placeholder_expander(log)
|
82
|
+
@hostname = Socket.gethostname
|
83
|
+
@key_formatter = key_formatter_create
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
def format(tag, time, record)
|
88
|
+
|
89
|
+
placeholder_values = {
|
90
|
+
'tag' => tag,
|
91
|
+
'tag_parts' => tag.split('.'),
|
92
|
+
'hostname' => @default_host,
|
93
|
+
'record' => record
|
94
|
+
}
|
95
|
+
|
96
|
+
placeholders = @placeholder_expander.prepare_placeholders(placeholder_values)
|
97
|
+
|
98
|
+
payload = {
|
99
|
+
# for v0.14 millisecs time precision
|
100
|
+
time: time.is_a?(Integer) ? time.to_i : time.to_f,
|
101
|
+
source: @source.nil? ? tag.to_s : @placeholder_expander.expand(@source, placeholders),
|
102
|
+
sourcetype: @placeholder_expander.expand(@sourcetype.to_s, placeholders),
|
103
|
+
host: @host.nil? ? @default_host : @placeholder_expander.expand(@host.to_s, placeholders),
|
104
|
+
index: @placeholder_expander.expand(@index.to_s, placeholders)
|
105
|
+
}
|
106
|
+
#payload[:index] = @index if @index
|
107
|
+
#payload[:source] = @source if @source
|
108
|
+
#payload[:sourcetype] = @sourcetype if @sourcetype
|
109
|
+
|
110
|
+
# delete nil fields otherwise will get formet error from HEC
|
111
|
+
%i[host index source sourcetype].each { |f| payload.delete f if payload[f].nil? }
|
112
|
+
|
113
|
+
if !record[@data_key].nil?
|
114
|
+
payload[@data_key] = record[@data_key]
|
115
|
+
end
|
116
|
+
|
117
|
+
if @all_items
|
118
|
+
payload[:event] = record
|
119
|
+
else
|
120
|
+
payload[:event] = record["message"]
|
121
|
+
end
|
122
|
+
|
123
|
+
format_for_api do
|
124
|
+
data = @data_formatter.call(tag, time, payload)
|
125
|
+
key = @key_formatter.call(payload)
|
126
|
+
[data, key]
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def write(chunk)
|
131
|
+
write_records_batch(chunk) do |batch|
|
132
|
+
records = batch.map{|(data, partition_key)|
|
133
|
+
{ data: data, partition_key: partition_key }
|
134
|
+
}
|
135
|
+
client.put_records(
|
136
|
+
stream_name: @stream_name,
|
137
|
+
records: records,
|
138
|
+
)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
private
|
143
|
+
|
144
|
+
def key_formatter_create
|
145
|
+
if @partition_key.nil?
|
146
|
+
->(record) { SecureRandom.hex(16) }
|
147
|
+
else
|
148
|
+
->(record) {
|
149
|
+
if !record.key?(@partition_key)
|
150
|
+
raise KeyNotFoundError.new(@partition_key, record)
|
151
|
+
end
|
152
|
+
record[@partition_key]
|
153
|
+
}
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
|
@@ -0,0 +1,78 @@
|
|
1
|
+
#
|
2
|
+
# Copyright 2014-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
3
|
+
#
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License"). You
|
5
|
+
# may not use this file except in compliance with the License. A copy of
|
6
|
+
# the License is located at
|
7
|
+
#
|
8
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
9
|
+
#
|
10
|
+
# or in the "license" file accompanying this file. This file is
|
11
|
+
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
|
12
|
+
# ANY KIND, either express or implied. See the License for the specific
|
13
|
+
# language governing permissions and limitations under the License.
|
14
|
+
|
15
|
+
require 'fluent/plugin/kinesis'
|
16
|
+
require 'fluent/plugin/kinesis_helper/aggregator'
|
17
|
+
|
18
|
+
module Fluent
|
19
|
+
module Plugin
|
20
|
+
class KinesisStreamsAggregatedOutput < KinesisOutput
|
21
|
+
Fluent::Plugin.register_output('kinesis_streams_aggregated', self)
|
22
|
+
include KinesisHelper::Aggregator::Mixin
|
23
|
+
|
24
|
+
RequestType = :streams_aggregated
|
25
|
+
BatchRequestLimitCount = 100_000
|
26
|
+
BatchRequestLimitSize = 1024 * 1024
|
27
|
+
include KinesisHelper::API::BatchRequest
|
28
|
+
|
29
|
+
config_param :stream_name, :string
|
30
|
+
config_param :fixed_partition_key, :string, default: nil
|
31
|
+
|
32
|
+
def configure(conf)
|
33
|
+
super
|
34
|
+
@partition_key_generator = create_partition_key_generator
|
35
|
+
@batch_request_max_size -= offset
|
36
|
+
@max_record_size -= offset
|
37
|
+
end
|
38
|
+
|
39
|
+
def format(tag, time, record)
|
40
|
+
format_for_api do
|
41
|
+
[@data_formatter.call(tag, time, record)]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def write(chunk)
|
46
|
+
write_records_batch(chunk) do |batch|
|
47
|
+
key = @partition_key_generator.call
|
48
|
+
records = batch.map{|(data)|data}
|
49
|
+
client.put_records(
|
50
|
+
stream_name: @stream_name,
|
51
|
+
records: [{
|
52
|
+
partition_key: key,
|
53
|
+
data: aggregator.aggregate(records, key),
|
54
|
+
}],
|
55
|
+
)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def offset
|
60
|
+
@offset ||= AggregateOffset + @partition_key_generator.call.size*2
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def size_of_values(record)
|
66
|
+
super(record) + RecordOffset
|
67
|
+
end
|
68
|
+
|
69
|
+
def create_partition_key_generator
|
70
|
+
if @fixed_partition_key.nil?
|
71
|
+
->() { SecureRandom.hex(16) }
|
72
|
+
else
|
73
|
+
->() { @fixed_partition_key }
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|