logstash-integration-aws 0.1.0.pre
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 +7 -0
- data/CHANGELOG.PRE.MERGE.md +658 -0
- data/CHANGELOG.md +15 -0
- data/CONTRIBUTORS +40 -0
- data/Gemfile +11 -0
- data/LICENSE +202 -0
- data/NOTICE.TXT +5 -0
- data/README.md +205 -0
- data/docs/codec-cloudfront.asciidoc +53 -0
- data/docs/codec-cloudtrail.asciidoc +45 -0
- data/docs/index.asciidoc +38 -0
- data/docs/input-cloudwatch.asciidoc +320 -0
- data/docs/input-s3.asciidoc +346 -0
- data/docs/input-sqs.asciidoc +287 -0
- data/docs/output-cloudwatch.asciidoc +321 -0
- data/docs/output-s3.asciidoc +442 -0
- data/docs/output-sns.asciidoc +166 -0
- data/docs/output-sqs.asciidoc +242 -0
- data/lib/logstash/codecs/cloudfront.rb +84 -0
- data/lib/logstash/codecs/cloudtrail.rb +47 -0
- data/lib/logstash/inputs/cloudwatch.rb +338 -0
- data/lib/logstash/inputs/s3.rb +466 -0
- data/lib/logstash/inputs/sqs.rb +196 -0
- data/lib/logstash/outputs/cloudwatch.rb +346 -0
- data/lib/logstash/outputs/s3/file_repository.rb +121 -0
- data/lib/logstash/outputs/s3/path_validator.rb +18 -0
- data/lib/logstash/outputs/s3/size_and_time_rotation_policy.rb +24 -0
- data/lib/logstash/outputs/s3/size_rotation_policy.rb +26 -0
- data/lib/logstash/outputs/s3/temporary_file.rb +71 -0
- data/lib/logstash/outputs/s3/temporary_file_factory.rb +129 -0
- data/lib/logstash/outputs/s3/time_rotation_policy.rb +26 -0
- data/lib/logstash/outputs/s3/uploader.rb +74 -0
- data/lib/logstash/outputs/s3/writable_directory_validator.rb +17 -0
- data/lib/logstash/outputs/s3/write_bucket_permission_validator.rb +60 -0
- data/lib/logstash/outputs/s3.rb +405 -0
- data/lib/logstash/outputs/sns.rb +133 -0
- data/lib/logstash/outputs/sqs.rb +167 -0
- data/lib/logstash/plugin_mixins/aws_config/generic.rb +54 -0
- data/lib/logstash/plugin_mixins/aws_config/v2.rb +93 -0
- data/lib/logstash/plugin_mixins/aws_config.rb +8 -0
- data/logstash-integration-aws.gemspec +52 -0
- data/spec/codecs/cloudfront_spec.rb +92 -0
- data/spec/codecs/cloudtrail_spec.rb +56 -0
- data/spec/fixtures/aws_credentials_file_sample_test.yml +2 -0
- data/spec/fixtures/aws_temporary_credentials_file_sample_test.yml +3 -0
- data/spec/fixtures/cloudfront.log +4 -0
- data/spec/fixtures/compressed.log.gee.zip +0 -0
- data/spec/fixtures/compressed.log.gz +0 -0
- data/spec/fixtures/compressed.log.gzip +0 -0
- data/spec/fixtures/invalid_utf8.gbk.log +2 -0
- data/spec/fixtures/json.log +2 -0
- data/spec/fixtures/json_with_message.log +2 -0
- data/spec/fixtures/multiline.log +6 -0
- data/spec/fixtures/multiple_compressed_streams.gz +0 -0
- data/spec/fixtures/uncompressed.log +2 -0
- data/spec/inputs/cloudwatch_spec.rb +85 -0
- data/spec/inputs/s3_spec.rb +610 -0
- data/spec/inputs/sincedb_spec.rb +17 -0
- data/spec/inputs/sqs_spec.rb +324 -0
- data/spec/integration/cloudwatch_spec.rb +25 -0
- data/spec/integration/dynamic_prefix_spec.rb +92 -0
- data/spec/integration/gzip_file_spec.rb +62 -0
- data/spec/integration/gzip_size_rotation_spec.rb +63 -0
- data/spec/integration/outputs/sqs_spec.rb +98 -0
- data/spec/integration/restore_from_crash_spec.rb +67 -0
- data/spec/integration/s3_spec.rb +66 -0
- data/spec/integration/size_rotation_spec.rb +59 -0
- data/spec/integration/sqs_spec.rb +110 -0
- data/spec/integration/stress_test_spec.rb +60 -0
- data/spec/integration/time_based_rotation_with_constant_write_spec.rb +60 -0
- data/spec/integration/time_based_rotation_with_stale_write_spec.rb +64 -0
- data/spec/integration/upload_current_file_on_shutdown_spec.rb +51 -0
- data/spec/outputs/cloudwatch_spec.rb +38 -0
- data/spec/outputs/s3/file_repository_spec.rb +143 -0
- data/spec/outputs/s3/size_and_time_rotation_policy_spec.rb +77 -0
- data/spec/outputs/s3/size_rotation_policy_spec.rb +41 -0
- data/spec/outputs/s3/temporary_file_factory_spec.rb +89 -0
- data/spec/outputs/s3/temporary_file_spec.rb +47 -0
- data/spec/outputs/s3/time_rotation_policy_spec.rb +60 -0
- data/spec/outputs/s3/uploader_spec.rb +69 -0
- data/spec/outputs/s3/writable_directory_validator_spec.rb +40 -0
- data/spec/outputs/s3/write_bucket_permission_validator_spec.rb +49 -0
- data/spec/outputs/s3_spec.rb +232 -0
- data/spec/outputs/sns_spec.rb +160 -0
- data/spec/plugin_mixin/aws_config_spec.rb +217 -0
- data/spec/spec_helper.rb +8 -0
- data/spec/support/helpers.rb +119 -0
- data/spec/unit/outputs/sqs_spec.rb +247 -0
- metadata +467 -0
@@ -0,0 +1,232 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "logstash/outputs/s3"
|
3
|
+
require "logstash/event"
|
4
|
+
require "logstash/codecs/line"
|
5
|
+
require "stud/temporary"
|
6
|
+
|
7
|
+
describe LogStash::Outputs::S3 do
|
8
|
+
let(:prefix) { "super/%{server}" }
|
9
|
+
let(:region) { "us-east-1" }
|
10
|
+
let(:bucket_name) { "mybucket" }
|
11
|
+
let(:options) { { "region" => region,
|
12
|
+
"bucket" => bucket_name,
|
13
|
+
"prefix" => prefix,
|
14
|
+
"restore" => false,
|
15
|
+
"access_key_id" => "access_key_id",
|
16
|
+
"secret_access_key" => "secret_access_key"
|
17
|
+
} }
|
18
|
+
let(:client) { Aws::S3::Client.new(stub_responses: true) }
|
19
|
+
let(:mock_bucket) { Aws::S3::Bucket.new(:name => bucket_name, :stub_responses => true, :client => client) }
|
20
|
+
let(:event) { LogStash::Event.new({ "server" => "overwatch" }) }
|
21
|
+
let(:event_encoded) { "super hype" }
|
22
|
+
let(:events_and_encoded) { { event => event_encoded } }
|
23
|
+
|
24
|
+
subject { described_class.new(options) }
|
25
|
+
|
26
|
+
before do
|
27
|
+
allow_any_instance_of(LogStash::Outputs::S3::WriteBucketPermissionValidator).to receive(:valid?).and_return(true)
|
28
|
+
end
|
29
|
+
|
30
|
+
context "#register configuration validation" do
|
31
|
+
describe "signature version" do
|
32
|
+
it "should set the signature version if specified" do
|
33
|
+
["v2", "v4"].each do |version|
|
34
|
+
s3 = described_class.new(options.merge({ "signature_version" => version }))
|
35
|
+
expect(s3.full_options).to include(:signature_version => version)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should omit the option completely if not specified" do
|
40
|
+
s3 = described_class.new(options)
|
41
|
+
expect(s3.full_options.has_key?(:signature_version)).to eql(false)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe "Access control list" do
|
46
|
+
context "when configured" do
|
47
|
+
["private", "public-read", "public-read-write", "authenticated-read", "aws-exec-read", "bucket-owner-read", "bucket-owner-full-control", "log-delivery-write"].each do |permission|
|
48
|
+
it "should return the configured ACL permissions: #{permission}" do
|
49
|
+
s3 = described_class.new(options.merge({ "canned_acl" => permission }))
|
50
|
+
expect(s3.upload_options).to include(:acl => permission)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
context "when not configured" do
|
56
|
+
it "uses private as the default" do
|
57
|
+
s3 = described_class.new(options)
|
58
|
+
expect(s3.upload_options).to include(:acl => "private")
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
describe "Multipart upload threshold" do
|
64
|
+
context "when configured" do
|
65
|
+
it "should use the configured threshold" do
|
66
|
+
threshold = 1 * 1024 * 1024
|
67
|
+
s3 = described_class.new(options.merge({ "upload_multipart_threshold" => threshold }))
|
68
|
+
expect(s3.upload_options).to include(:multipart_threshold => threshold)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
context "when not configured" do
|
73
|
+
it "should use 15MB as the default" do
|
74
|
+
s3 = described_class.new(options)
|
75
|
+
expect(s3.upload_options).to include(:multipart_threshold => 15 * 1024 * 1024)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
describe "Service Side Encryption" do
|
81
|
+
|
82
|
+
context "when configured" do
|
83
|
+
it "should be configure" do
|
84
|
+
s3 = described_class.new(options.merge({ "server_side_encryption" => true }))
|
85
|
+
expect(s3.upload_options).to include(:server_side_encryption => "AES256")
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
context "when algorithm is configured" do
|
90
|
+
["AES256", "aws:kms"].each do |sse|
|
91
|
+
it "should return the configured SSE: #{sse}" do
|
92
|
+
s3 = described_class.new(options.merge({ "server_side_encryption" => true, "server_side_encryption_algorithm" => sse }))
|
93
|
+
expect(s3.upload_options).to include(:server_side_encryption => sse)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
context "when using SSE with KMS and custom key" do
|
99
|
+
it "should return the configured KMS key" do
|
100
|
+
s3 = described_class.new(options.merge({ "server_side_encryption" => true, "server_side_encryption_algorithm" => "aws:kms", "ssekms_key_id" => "test"}))
|
101
|
+
expect(s3.upload_options).to include(:server_side_encryption => "aws:kms")
|
102
|
+
expect(s3.upload_options).to include(:ssekms_key_id => "test")
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
context "when using SSE with KMS but no custom key" do
|
107
|
+
it "should return the configured KMS key" do
|
108
|
+
s3 = described_class.new(options.merge({ "server_side_encryption" => true, "server_side_encryption_algorithm" => "aws:kms"}))
|
109
|
+
expect(s3.upload_options).to include(:server_side_encryption => "aws:kms")
|
110
|
+
expect(s3.upload_options).to include(:ssekms_key_id => nil)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
context "when not configured" do
|
115
|
+
it "should not be configured" do
|
116
|
+
s3 = described_class.new(options)
|
117
|
+
expect(s3.upload_options).to include(:server_side_encryption => nil)
|
118
|
+
expect(s3.upload_options).to include(:ssekms_key_id => nil)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
describe "Storage Class" do
|
124
|
+
context "when configured" do
|
125
|
+
["STANDARD", "REDUCED_REDUNDANCY", "STANDARD_IA", "ONEZONE_IA"].each do |storage_class|
|
126
|
+
it "should return the configured storage class: #{storage_class}" do
|
127
|
+
s3 = described_class.new(options.merge({ "storage_class" => storage_class }))
|
128
|
+
expect(s3.upload_options).to include(:storage_class => storage_class)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
context "when not configured" do
|
134
|
+
it "uses STANDARD as the default" do
|
135
|
+
s3 = described_class.new(options)
|
136
|
+
expect(s3.upload_options).to include(:storage_class => "STANDARD")
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
describe "temporary directory" do
|
142
|
+
let(:temporary_directory) { Stud::Temporary.pathname }
|
143
|
+
let(:options) { super().merge({ "temporary_directory" => temporary_directory }) }
|
144
|
+
|
145
|
+
it "creates the directory when it doesn't exist" do
|
146
|
+
expect(Dir.exist?(temporary_directory)).to be_falsey
|
147
|
+
subject.register
|
148
|
+
expect(Dir.exist?(temporary_directory)).to be_truthy
|
149
|
+
end
|
150
|
+
|
151
|
+
it "raises an error if we cannot write to the directory" do
|
152
|
+
expect(LogStash::Outputs::S3::WritableDirectoryValidator).to receive(:valid?).with(temporary_directory).and_return(false)
|
153
|
+
expect { subject.register }.to raise_error(LogStash::ConfigurationError)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
it "validates the prefix" do
|
158
|
+
s3 = described_class.new(options.merge({ "prefix" => "`no\><^" }))
|
159
|
+
expect { s3.register }.to raise_error(LogStash::ConfigurationError)
|
160
|
+
end
|
161
|
+
|
162
|
+
describe "additional_settings" do
|
163
|
+
context "supported settings" do
|
164
|
+
let(:additional_settings) do
|
165
|
+
{ "additional_settings" => { "force_path_style" => 'true', "ssl_verify_peer" => 'false', "profile" => 'logstash' } }
|
166
|
+
end
|
167
|
+
|
168
|
+
it "validates the prefix" do
|
169
|
+
expect(Aws::S3::Bucket).to receive(:new).twice.
|
170
|
+
with(anything, hash_including(:force_path_style => true, :ssl_verify_peer => false, :profile => 'logstash')).
|
171
|
+
and_call_original
|
172
|
+
described_class.new(options.merge(additional_settings)).register
|
173
|
+
end
|
174
|
+
end
|
175
|
+
context "when using a non existing setting" do
|
176
|
+
let(:additional_settings) do
|
177
|
+
{ "additional_settings" => { "doesnt_exist" => true } }
|
178
|
+
end
|
179
|
+
|
180
|
+
it "raises an error" do
|
181
|
+
plugin = described_class.new(options.merge(additional_settings))
|
182
|
+
expect { plugin.register }.to raise_error(ArgumentError)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
it "allow to not validate credentials" do
|
188
|
+
s3 = described_class.new(options.merge({"validate_credentials_on_root_bucket" => false}))
|
189
|
+
expect_any_instance_of(LogStash::Outputs::S3::WriteBucketPermissionValidator).not_to receive(:valid?).with(any_args)
|
190
|
+
s3.register
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
context "receiving events" do
|
195
|
+
before do
|
196
|
+
allow(subject).to receive(:bucket_resource).and_return(mock_bucket)
|
197
|
+
subject.register
|
198
|
+
end
|
199
|
+
|
200
|
+
after do
|
201
|
+
subject.close
|
202
|
+
end
|
203
|
+
|
204
|
+
it "uses `Event#sprintf` for the prefix" do
|
205
|
+
expect(event).to receive(:sprintf).with(prefix).and_return("super/overwatch")
|
206
|
+
subject.multi_receive_encoded(events_and_encoded)
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
describe "aws service" do
|
211
|
+
context 'us-east-1' do
|
212
|
+
let(:region) { 'us-east-1' }
|
213
|
+
it "sets endpoint" do
|
214
|
+
expect( subject.send(:bucket_resource).client.config.endpoint.to_s ).to eql 'https://s3.us-east-1.amazonaws.com'
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
context 'ap-east-1' do
|
219
|
+
let(:region) { 'ap-east-1' }
|
220
|
+
it "sets endpoint" do
|
221
|
+
expect( subject.send(:bucket_resource).client.config.endpoint.to_s ).to eql 'https://s3.ap-east-1.amazonaws.com'
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
context 'cn-northwest-1' do
|
226
|
+
let(:region) { 'cn-northwest-1' }
|
227
|
+
it "sets endpoint" do
|
228
|
+
expect( subject.send(:bucket_resource).client.config.endpoint.to_s ).to eql 'https://s3.cn-northwest-1.amazonaws.com.cn'
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
@@ -0,0 +1,160 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "logstash/devutils/rspec/spec_helper"
|
3
|
+
require 'logstash/outputs/sns'
|
4
|
+
require 'logstash/event'
|
5
|
+
require "logstash/plugin_mixins/aws_config"
|
6
|
+
|
7
|
+
require "aws-sdk-sns"
|
8
|
+
|
9
|
+
describe LogStash::Outputs::Sns do
|
10
|
+
let(:arn) { "arn:aws:sns:us-east-1:999999999:logstash-test-sns-topic" }
|
11
|
+
let(:sns_subject) { "The Plain in Spain" }
|
12
|
+
let(:sns_message) { "That's where the rain falls, plainly." }
|
13
|
+
let(:mock_client) { double("Aws::SNS::Client") }
|
14
|
+
let(:instance) {
|
15
|
+
allow(Aws::SNS::Client).to receive(:new).and_return(mock_client)
|
16
|
+
inst = LogStash::Outputs::Sns.new
|
17
|
+
allow(inst).to receive(:publish_boot_message_arn).and_return(nil)
|
18
|
+
inst.register
|
19
|
+
inst
|
20
|
+
}
|
21
|
+
|
22
|
+
describe "receiving an event" do
|
23
|
+
let(:expected_subject) { double("expected_subject")}
|
24
|
+
subject {
|
25
|
+
inst = instance
|
26
|
+
allow(inst).to receive(:send_sns_message).with(any_args)
|
27
|
+
allow(inst).to receive(:event_subject).
|
28
|
+
with(any_args).
|
29
|
+
and_return(expected_subject)
|
30
|
+
inst.receive(event)
|
31
|
+
inst
|
32
|
+
}
|
33
|
+
|
34
|
+
shared_examples("publishing correctly") do
|
35
|
+
it "should send a message to the correct ARN if the event has 'arn' set" do
|
36
|
+
expect(subject).to have_received(:send_sns_message).with(arn, anything, anything)
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should send the message" do
|
40
|
+
expect(subject).to have_received(:send_sns_message).with(anything, anything, expected_message)
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should send the subject" do
|
44
|
+
expect(subject).to have_received(:send_sns_message).with(anything, expected_subject, anything)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe "with an explicit message" do
|
49
|
+
let(:expected_subject) { sns_subject }
|
50
|
+
let(:expected_message) { sns_message }
|
51
|
+
let(:event) { LogStash::Event.new("sns" => arn, "sns_subject" => sns_subject,
|
52
|
+
"sns_message" => sns_message) }
|
53
|
+
include_examples("publishing correctly")
|
54
|
+
end
|
55
|
+
|
56
|
+
describe "without an explicit message" do
|
57
|
+
# Testing codecs sucks. It'd be nice if codecs had to implement some sort of encode_sync method
|
58
|
+
let(:expected_message) {
|
59
|
+
c = subject.codec.clone
|
60
|
+
result = nil;
|
61
|
+
c.on_event {|event, encoded| result = encoded }
|
62
|
+
c.encode(event)
|
63
|
+
result
|
64
|
+
}
|
65
|
+
let(:event) { LogStash::Event.new("sns" => arn, "sns_subject" => sns_subject) }
|
66
|
+
|
67
|
+
include_examples("publishing correctly")
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe "determining the subject" do
|
72
|
+
it "should return 'sns_subject' when set" do
|
73
|
+
event = LogStash::Event.new("sns_subject" => "foo")
|
74
|
+
expect(subject.send(:event_subject, event)).to eql("foo")
|
75
|
+
end
|
76
|
+
|
77
|
+
it "should return the sns subject as JSON if not a string" do
|
78
|
+
event = LogStash::Event.new("sns_subject" => ["foo", "bar"])
|
79
|
+
expect(subject.send(:event_subject, event)).to eql(LogStash::Json.dump(["foo", "bar"]))
|
80
|
+
end
|
81
|
+
|
82
|
+
it "should return the host if 'sns_subject' not set" do
|
83
|
+
event = LogStash::Event.new("host" => "foo")
|
84
|
+
expect(subject.send(:event_subject, event)).to eql("foo")
|
85
|
+
end
|
86
|
+
|
87
|
+
it "should return the host name (ECS compatibility) if 'sns_subject' not set" do
|
88
|
+
event = LogStash::Event.new
|
89
|
+
event.set('[host][hostname]', 'server1')
|
90
|
+
expect(subject.send(:event_subject, event)).to eql('server1')
|
91
|
+
end
|
92
|
+
|
93
|
+
it "should return the host IP (ECS compatibility) if 'sns_subject' not set" do
|
94
|
+
event = LogStash::Event.new
|
95
|
+
event.set('host.geo.name', 'Vychodne Pobrezie')
|
96
|
+
expect(subject.send(:event_subject, event)).to eql(LogStash::Outputs::Sns::NO_SUBJECT)
|
97
|
+
end
|
98
|
+
|
99
|
+
it "should return no subject when no info in host object (ECS compatibility) if 'sns_subject' not set" do
|
100
|
+
event = LogStash::Event.new
|
101
|
+
event.set('[host][ip]', '192.168.1.111')
|
102
|
+
expect(subject.send(:event_subject, event)).to eql('192.168.1.111')
|
103
|
+
end
|
104
|
+
|
105
|
+
it "should return 'NO SUBJECT' when subject cannot be determined" do
|
106
|
+
event = LogStash::Event.new("foo" => "bar")
|
107
|
+
expect(subject.send(:event_subject, event)).to eql(LogStash::Outputs::Sns::NO_SUBJECT)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
describe "sending an SNS notification" do
|
112
|
+
let(:good_publish_args) {
|
113
|
+
{
|
114
|
+
:topic_arn => arn,
|
115
|
+
:subject => sns_subject,
|
116
|
+
:message => sns_message
|
117
|
+
}
|
118
|
+
}
|
119
|
+
let(:long_message) { "A" * (LogStash::Outputs::Sns::MAX_MESSAGE_SIZE_IN_BYTES + 1) }
|
120
|
+
let(:long_subject) { "S" * (LogStash::Outputs::Sns::MAX_SUBJECT_SIZE_IN_CHARACTERS + 1) }
|
121
|
+
subject { instance }
|
122
|
+
|
123
|
+
it "should raise an ArgumentError if no arn is provided" do
|
124
|
+
expect {
|
125
|
+
subject.send(:send_sns_message, nil, sns_subject, sns_message)
|
126
|
+
}.to raise_error(ArgumentError)
|
127
|
+
end
|
128
|
+
|
129
|
+
it "should send a well formed message through to SNS" do
|
130
|
+
expect(mock_client).to receive(:publish).with(good_publish_args)
|
131
|
+
subject.send(:send_sns_message, arn, sns_subject, sns_message)
|
132
|
+
end
|
133
|
+
|
134
|
+
it "should attempt to publish a boot message" do
|
135
|
+
expect(subject).to have_received(:publish_boot_message_arn).once
|
136
|
+
x = case "foo"
|
137
|
+
when "bar"
|
138
|
+
"hello"
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
it "should truncate long messages before sending" do
|
143
|
+
max_size = LogStash::Outputs::Sns::MAX_MESSAGE_SIZE_IN_BYTES
|
144
|
+
expect(mock_client).to receive(:publish) {|args|
|
145
|
+
expect(args[:message].bytesize).to eql(max_size)
|
146
|
+
}
|
147
|
+
|
148
|
+
subject.send(:send_sns_message, arn, sns_subject, long_message)
|
149
|
+
end
|
150
|
+
|
151
|
+
it "should truncate long subjects before sending" do
|
152
|
+
max_size = LogStash::Outputs::Sns::MAX_SUBJECT_SIZE_IN_CHARACTERS
|
153
|
+
expect(mock_client).to receive(:publish) {|args|
|
154
|
+
expect(args[:subject].bytesize).to eql(max_size)
|
155
|
+
}
|
156
|
+
|
157
|
+
subject.send(:send_sns_message, arn, long_subject, sns_message)
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
@@ -0,0 +1,217 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "logstash/devutils/rspec/spec_helper"
|
3
|
+
require "logstash/plugin_mixins/aws_config"
|
4
|
+
require 'timecop'
|
5
|
+
|
6
|
+
class DummyInputAwsConfigV2 < LogStash::Inputs::Base
|
7
|
+
include LogStash::PluginMixins::AwsConfig::V2
|
8
|
+
|
9
|
+
def aws_service_endpoint(region)
|
10
|
+
{ :dummy_input_aws_config_region => "#{region}.awswebservice.local" }
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class DummyInputAwsConfigV2NoRegionMethod < LogStash::Inputs::Base
|
15
|
+
include LogStash::PluginMixins::AwsConfig::V2
|
16
|
+
end
|
17
|
+
|
18
|
+
describe LogStash::PluginMixins::AwsConfig::V2 do
|
19
|
+
let(:settings) { {} }
|
20
|
+
|
21
|
+
subject { DummyInputAwsConfigV2.new(settings).aws_options_hash }
|
22
|
+
|
23
|
+
describe 'config credential' do
|
24
|
+
subject { DummyInputAwsConfigV2.new(settings).aws_options_hash[:credentials] }
|
25
|
+
|
26
|
+
context 'in credential file' do
|
27
|
+
let(:settings) { { 'aws_credentials_file' => File.join(File.dirname(__FILE__), '..', 'fixtures/aws_credentials_file_sample_test.yml') } }
|
28
|
+
|
29
|
+
it 'should support reading configuration from a yaml file' do
|
30
|
+
expect(subject.access_key_id).to eq("1234")
|
31
|
+
expect(subject.secret_access_key).to eq("secret")
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
context 'inline' do
|
36
|
+
context 'temporary credential' do
|
37
|
+
let(:settings) { { 'access_key_id' => '1234', 'secret_access_key' => 'secret', 'session_token' => 'session_token' } }
|
38
|
+
|
39
|
+
it "should support passing as key, value, and session_token" do
|
40
|
+
expect(subject.access_key_id).to eq(settings['access_key_id'])
|
41
|
+
expect(subject.secret_access_key).to eq(settings['secret_access_key'])
|
42
|
+
expect(subject.session_token).to eq(settings['session_token'])
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
context 'normal credential' do
|
47
|
+
let(:settings) { { 'access_key_id' => '1234', 'secret_access_key' => 'secret' } }
|
48
|
+
|
49
|
+
it 'should support passing credentials as key, value' do
|
50
|
+
expect(subject.access_key_id).to eq(settings['access_key_id'])
|
51
|
+
expect(subject.secret_access_key).to eq(settings['secret_access_key'])
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
context 'role arn is provided' do
|
56
|
+
let(:settings) { { 'role_arn' => 'arn:aws:iam::012345678910:role/foo', 'region' => 'us-west-2' } }
|
57
|
+
let(:sts_double) { instance_double(Aws::STS::Client) }
|
58
|
+
let(:now) { Time.now }
|
59
|
+
let(:expiration) { Time.at(now.to_i + 3600) }
|
60
|
+
let(:temp_credentials) {
|
61
|
+
double(credentials:
|
62
|
+
double(
|
63
|
+
access_key_id: '1234',
|
64
|
+
secret_access_key: 'secret',
|
65
|
+
session_token: 'session_token',
|
66
|
+
expiration: expiration.to_s,
|
67
|
+
)
|
68
|
+
)
|
69
|
+
}
|
70
|
+
let(:new_temp_credentials) {
|
71
|
+
double(credentials:
|
72
|
+
double(
|
73
|
+
access_key_id: '5678',
|
74
|
+
secret_access_key: 'secret1',
|
75
|
+
session_token: 'session_token1',
|
76
|
+
expiration: expiration.to_s,
|
77
|
+
)
|
78
|
+
)
|
79
|
+
}
|
80
|
+
|
81
|
+
before do
|
82
|
+
allow(Aws::STS::Client).to receive(:new).and_return(sts_double)
|
83
|
+
allow(sts_double).to receive(:assume_role) {
|
84
|
+
if Time.now < expiration
|
85
|
+
temp_credentials
|
86
|
+
else
|
87
|
+
new_temp_credentials
|
88
|
+
end
|
89
|
+
}
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'supports passing role_arn' do
|
93
|
+
Timecop.freeze(now) do
|
94
|
+
expect(subject.credentials.access_key_id).to eq('1234')
|
95
|
+
expect(subject.credentials.secret_access_key).to eq('secret')
|
96
|
+
expect(subject.credentials.session_token).to eq('session_token')
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'rotates the keys once they expire' do
|
101
|
+
Timecop.freeze(Time.at(expiration.to_i + 100)) do
|
102
|
+
expect(subject.credentials.access_key_id).to eq('5678')
|
103
|
+
expect(subject.credentials.secret_access_key).to eq('secret1')
|
104
|
+
expect(subject.credentials.session_token).to eq('session_token1')
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
context 'role arn with credentials' do
|
110
|
+
|
111
|
+
let(:settings) do
|
112
|
+
{
|
113
|
+
'role_arn' => 'arn:aws:iam::012345678910:role/foo',
|
114
|
+
'region' => 'us-west-2',
|
115
|
+
|
116
|
+
'access_key_id' => '12345678',
|
117
|
+
'secret_access_key' => 'secret',
|
118
|
+
'session_token' => 'session_token',
|
119
|
+
|
120
|
+
'proxy_uri' => 'http://a-proxy.net:1234'
|
121
|
+
}
|
122
|
+
end
|
123
|
+
|
124
|
+
let(:aws_options_hash) { DummyInputAwsConfigV2NoRegionMethod.new(settings).aws_options_hash }
|
125
|
+
|
126
|
+
before do
|
127
|
+
allow_any_instance_of(Aws::AssumeRoleCredentials).to receive(:refresh) # called from #initialize
|
128
|
+
end
|
129
|
+
|
130
|
+
it 'uses credentials' do
|
131
|
+
subject = aws_options_hash[:credentials]
|
132
|
+
expect( subject ).to be_a Aws::AssumeRoleCredentials
|
133
|
+
expect( subject.client ).to be_a Aws::STS::Client
|
134
|
+
expect( credentials = subject.client.config.credentials ).to be_a Aws::Credentials
|
135
|
+
expect( credentials.access_key_id ).to eql '12345678'
|
136
|
+
end
|
137
|
+
|
138
|
+
it 'sets up proxy on client and region' do
|
139
|
+
subject = aws_options_hash[:credentials]
|
140
|
+
expect( subject.client.config.http_proxy ).to eql 'http://a-proxy.net:1234'
|
141
|
+
expect( subject.client.config.region ).to eql 'us-west-2' # probably redundant (kept for backwards compat)
|
142
|
+
end
|
143
|
+
|
144
|
+
it 'sets up proxy top level' do # setting in on the client isn't enough!
|
145
|
+
expect( aws_options_hash[:http_proxy] ).to eql 'http://a-proxy.net:1234'
|
146
|
+
end
|
147
|
+
|
148
|
+
it 'sets up region top-level' do
|
149
|
+
# NOTE: this one is required for real with role_arn :
|
150
|
+
expect( aws_options_hash[:region] ).to eql 'us-west-2'
|
151
|
+
end
|
152
|
+
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
describe 'config proxy' do
|
158
|
+
let(:proxy) { "http://localhost:1234" }
|
159
|
+
let(:settings) { { 'access_key_id' => '1234', 'secret_access_key' => 'secret', 'region' => 'us-west-2', 'proxy_uri' => proxy } }
|
160
|
+
|
161
|
+
it "should set the http_proxy option" do
|
162
|
+
expect(subject[:http_proxy]).to eql(proxy)
|
163
|
+
end
|
164
|
+
|
165
|
+
it "should not set the legacy http proxy option" do
|
166
|
+
expect(subject[:proxy_uri]).not_to eql(proxy)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
describe 'config region' do
|
171
|
+
context "when the class implement `#aws_service_endpoint`" do
|
172
|
+
context 'region provided' do
|
173
|
+
let(:settings) { { 'access_key_id' => '1234', 'secret_access_key' => 'secret', 'region' => 'us-west-2' } }
|
174
|
+
|
175
|
+
it 'should use provided region to generate the endpoint configuration' do
|
176
|
+
expect(subject).to include(:dummy_input_aws_config_region => "us-west-2.awswebservice.local")
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
context "region not provided" do
|
181
|
+
let(:settings) { { 'access_key_id' => '1234', 'secret_access_key' => 'secret'} }
|
182
|
+
|
183
|
+
it 'should use default region to generate the endpoint configuration' do
|
184
|
+
expect(subject).to include(:dummy_input_aws_config_region => "us-east-1.awswebservice.local")
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
context "when the classe doesn't implement `#aws_service_endpoint`" do
|
190
|
+
subject { DummyInputAwsConfigV2NoRegionMethod.new(settings).aws_options_hash }
|
191
|
+
|
192
|
+
context 'region provided' do
|
193
|
+
let(:settings) { { 'access_key_id' => '1234', 'secret_access_key' => 'secret', 'region' => 'us-west-2' } }
|
194
|
+
|
195
|
+
it 'should use provided region to generate the endpoint configuration' do
|
196
|
+
expect(subject[:region]).to eq("us-west-2")
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
context "region not provided" do
|
201
|
+
let(:settings) { { 'access_key_id' => '1234', 'secret_access_key' => 'secret'} }
|
202
|
+
|
203
|
+
it 'should use default region to generate the endpoint configuration' do
|
204
|
+
expect(subject[:region]).to eq("us-east-1")
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
context 'when we arent providing credentials' do
|
211
|
+
let(:settings) { {} }
|
212
|
+
it 'should always return a hash' do
|
213
|
+
expect(subject).to eq({ :dummy_input_aws_config_region => "us-east-1.awswebservice.local" })
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
end
|
data/spec/spec_helper.rb
ADDED