logstash-output-email 0.1.0
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 +15 -0
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/Rakefile +6 -0
- data/lib/logstash/outputs/email.rb +303 -0
- data/logstash-output-email.gemspec +28 -0
- data/rakelib/publish.rake +9 -0
- data/rakelib/vendor.rake +169 -0
- data/spec/outputs/email_spec.rb +173 -0
- metadata +88 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
MGExYjgwYzBlYTE4OGRjY2I1YjQ0MzdkZjNjZjliNWEwNDM4OTcxZg==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
OTQ5MWUwNjhjNzNjMDQ5OTk5YzAyOWE4MzRkMzIwNWJiYTJmYzEwNQ==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
NzRiYmFjMjM5MjliYzVmZDQwNzQ3Mjk0NDBlMjg3ZGJkZGY4YjkwZDQ2Zjkw
|
10
|
+
MDc1OGQ2Mzc4N2U4NTM0ODVlMGQ5MTQzMjE2ZDc1NTM4NWIwYThjMjU4OWUw
|
11
|
+
NGM1YmNmYTVmM2U1ZjVlYTIwYzk1ZmI4ZjY3YTJjODQyMjg3YTA=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
MGE4Y2U3ZjM2M2QyNzIxNDcxYWUwNmFlNThlMjZlNGEzZDA2M2Q4ZTMyMzQw
|
14
|
+
MTljZmMzYzJmZDA5Y2M4ZjRiY2U4MzIzYWM5YjNlNThkZDM4NGFlNWY3Nzk2
|
15
|
+
MTc2ZTRlYThhYjU2MjhjNjllZTcxMmI1MDQ3ODAzZjkxNGVkZDg=
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,303 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require "logstash/outputs/base"
|
3
|
+
require "logstash/namespace"
|
4
|
+
|
5
|
+
# Send email when an output is received. Alternatively, you may include or
|
6
|
+
# exclude the email output execution using conditionals.
|
7
|
+
class LogStash::Outputs::Email < LogStash::Outputs::Base
|
8
|
+
|
9
|
+
config_name "email"
|
10
|
+
milestone 1
|
11
|
+
|
12
|
+
# This setting is deprecated in favor of Logstash's "conditionals" feature
|
13
|
+
# If you were using this setting previously, please use conditionals instead.
|
14
|
+
#
|
15
|
+
# If you need help converting your older 'match' setting to a conditional,
|
16
|
+
# I welcome you to join the #logstash irc channel on freenode or to email
|
17
|
+
# the logstash-users@googlegroups.com mailling list and ask for help! :)
|
18
|
+
config :match, :validate => :hash, :deprecated => true
|
19
|
+
|
20
|
+
# The fully-qualified email address to send the email to.
|
21
|
+
#
|
22
|
+
# This field also accepts a comma-separated string of addresses, for example:
|
23
|
+
# "me@host.com, you@host.com"
|
24
|
+
#
|
25
|
+
# You can also use dynamic fields from the event with the %{fieldname} syntax.
|
26
|
+
config :to, :validate => :string, :required => true
|
27
|
+
|
28
|
+
# The fully-qualified email address for the From: field in the email.
|
29
|
+
config :from, :validate => :string, :default => "logstash.alert@nowhere.com"
|
30
|
+
|
31
|
+
# The fully qualified email address for the Reply-To: field.
|
32
|
+
config :replyto, :validate => :string
|
33
|
+
|
34
|
+
# The fully-qualified email address(es) to include as cc: address(es).
|
35
|
+
#
|
36
|
+
# This field also accepts a comma-separated string of addresses, for example:
|
37
|
+
# "me@host.com, you@host.com"
|
38
|
+
config :cc, :validate => :string
|
39
|
+
|
40
|
+
# How Logstash should send the email, either via SMTP or by invoking sendmail.
|
41
|
+
config :via, :validate => :string, :default => "smtp"
|
42
|
+
|
43
|
+
# Specify the options to use:
|
44
|
+
#
|
45
|
+
# Via SMTP: smtpIporHost, port, domain, userName, password, authenticationType, starttls
|
46
|
+
#
|
47
|
+
# Via sendmail: location, arguments
|
48
|
+
#
|
49
|
+
# If you do not specify any `options`, you will get the following equivalent code set in
|
50
|
+
# every new mail object:
|
51
|
+
#
|
52
|
+
# Mail.defaults do
|
53
|
+
# delivery_method :smtp, { :smtpIporHost => "localhost",
|
54
|
+
# :port => 25,
|
55
|
+
# :domain => 'localhost.localdomain',
|
56
|
+
# :userName => nil,
|
57
|
+
# :password => nil,
|
58
|
+
# :authenticationType => nil,(plain, login and cram_md5)
|
59
|
+
# :starttls => true }
|
60
|
+
#
|
61
|
+
# retriever_method :pop3, { :address => "localhost",
|
62
|
+
# :port => 995,
|
63
|
+
# :user_name => nil,
|
64
|
+
# :password => nil,
|
65
|
+
# :enable_ssl => true }
|
66
|
+
#
|
67
|
+
# Mail.delivery_method.new #=> Mail::SMTP instance
|
68
|
+
# Mail.retriever_method.new #=> Mail::POP3 instance
|
69
|
+
# end
|
70
|
+
#
|
71
|
+
# Each mail object inherits the defaults set in Mail.delivery_method. However, on
|
72
|
+
# a per email basis, you can override the method:
|
73
|
+
#
|
74
|
+
# mail.delivery_method :sendmail
|
75
|
+
#
|
76
|
+
# Or you can override the method and pass in settings:
|
77
|
+
#
|
78
|
+
# mail.delivery_method :sendmail, { :address => 'some.host' }
|
79
|
+
#
|
80
|
+
# You can also just modify the settings:
|
81
|
+
#
|
82
|
+
# mail.delivery_settings = { :address => 'some.host' }
|
83
|
+
#
|
84
|
+
# The hash you supply is just merged against the defaults with "merge!" and the result
|
85
|
+
# assigned to the mail object. For instance, the above example will change only the
|
86
|
+
# `:address` value of the global `smtp_settings` to be 'some.host', retaining all other values.
|
87
|
+
config :options, :validate => :hash, :default => {}
|
88
|
+
|
89
|
+
# Subject: for the email.
|
90
|
+
config :subject, :validate => :string, :default => ""
|
91
|
+
|
92
|
+
# Body for the email - plain text only.
|
93
|
+
config :body, :validate => :string, :default => ""
|
94
|
+
|
95
|
+
# HTML Body for the email, which may contain HTML markup.
|
96
|
+
config :htmlbody, :validate => :string, :default => ""
|
97
|
+
|
98
|
+
# Attachments - specify the name(s) and location(s) of the files.
|
99
|
+
config :attachments, :validate => :array, :default => []
|
100
|
+
|
101
|
+
# contenttype : for multipart messages, set the content-type and/or charset of the HTML part.
|
102
|
+
# NOTE: this may not be functional (KH)
|
103
|
+
config :contenttype, :validate => :string, :default => "text/html; charset=UTF-8"
|
104
|
+
|
105
|
+
public
|
106
|
+
def register
|
107
|
+
require "mail"
|
108
|
+
|
109
|
+
# Mail uses instance_eval which changes the scope of self so @options is
|
110
|
+
# inaccessible from inside 'Mail.defaults'. So set a local variable instead.
|
111
|
+
options = @options
|
112
|
+
|
113
|
+
if @via == "smtp"
|
114
|
+
Mail.defaults do
|
115
|
+
delivery_method :smtp, {
|
116
|
+
:address => options.fetch("smtpIporHost", "localhost"),
|
117
|
+
:port => options.fetch("port", 25),
|
118
|
+
:domain => options.fetch("domain", "localhost"),
|
119
|
+
:user_name => options.fetch("userName", nil),
|
120
|
+
:password => options.fetch("password", nil),
|
121
|
+
:authentication => options.fetch("authenticationType", nil),
|
122
|
+
:enable_starttls_auto => options.fetch("starttls", false),
|
123
|
+
:debug => options.fetch("debug", false)
|
124
|
+
}
|
125
|
+
end
|
126
|
+
elsif @via == 'sendmail'
|
127
|
+
Mail.defaults do
|
128
|
+
delivery_method :sendmail
|
129
|
+
end
|
130
|
+
else
|
131
|
+
Mail.defaults do
|
132
|
+
delivery_method :@via, options
|
133
|
+
end
|
134
|
+
end # @via tests
|
135
|
+
@logger.debug("Email Output Registered!", :config => @config)
|
136
|
+
end # def register
|
137
|
+
|
138
|
+
public
|
139
|
+
def receive(event)
|
140
|
+
return unless output?(event)
|
141
|
+
@logger.debug("Event being tested for Email", :tags => @tags, :event => event)
|
142
|
+
# Set Intersection - returns a new array with the items that are the same between the two
|
143
|
+
if !@tags.empty? && (event["tags"] & @tags).size == 0
|
144
|
+
# Skip events that have no tags in common with what we were configured
|
145
|
+
@logger.debug("No Tags match for Email Output!")
|
146
|
+
return
|
147
|
+
end
|
148
|
+
|
149
|
+
@logger.debug? && @logger.debug("Match data for Email - ", :match => @match)
|
150
|
+
successful = false
|
151
|
+
matchName = ""
|
152
|
+
operator = ""
|
153
|
+
|
154
|
+
# TODO(sissel): Delete this once match support is removed.
|
155
|
+
@match && @match.each do |name, query|
|
156
|
+
if successful
|
157
|
+
break
|
158
|
+
else
|
159
|
+
matchName = name
|
160
|
+
end
|
161
|
+
# now loop over the csv query
|
162
|
+
queryArray = query.split(',')
|
163
|
+
index = 1
|
164
|
+
while index < queryArray.length
|
165
|
+
field = queryArray.at(index -1)
|
166
|
+
value = queryArray.at(index)
|
167
|
+
index = index + 2
|
168
|
+
if field == ""
|
169
|
+
if value.downcase == "and"
|
170
|
+
operator = "and"
|
171
|
+
elsif value.downcase == "or"
|
172
|
+
operator = "or"
|
173
|
+
else
|
174
|
+
operator = "or"
|
175
|
+
@logger.error("Operator Provided Is Not Found, Currently We Only Support AND/OR Values! - defaulting to OR")
|
176
|
+
end
|
177
|
+
else
|
178
|
+
hasField = event[field]
|
179
|
+
@logger.debug? and @logger.debug("Does Event Contain Field - ", :hasField => hasField)
|
180
|
+
isValid = false
|
181
|
+
# if we have maching field and value is wildcard - we have a success
|
182
|
+
if hasField
|
183
|
+
if value == "*"
|
184
|
+
isValid = true
|
185
|
+
else
|
186
|
+
# we get an array so we need to loop over the values and find if we have a match
|
187
|
+
eventFieldValues = event[field]
|
188
|
+
@logger.debug? and @logger.debug("Event Field Values - ", :eventFieldValues => eventFieldValues)
|
189
|
+
eventFieldValues = [eventFieldValues] if not eventFieldValues.respond_to?(:each)
|
190
|
+
eventFieldValues.each do |eventFieldValue|
|
191
|
+
isValid = validateValue(eventFieldValue, value)
|
192
|
+
if isValid # no need to iterate any further
|
193
|
+
@logger.debug("VALID CONDITION FOUND - ", :eventFieldValue => eventFieldValue, :value => value)
|
194
|
+
break
|
195
|
+
end
|
196
|
+
end # end eventFieldValues.each do
|
197
|
+
end # end value == "*"
|
198
|
+
end # end hasField
|
199
|
+
# if we have an AND operator and we have a successful == false break
|
200
|
+
if operator == "and" && !isValid
|
201
|
+
successful = false
|
202
|
+
elsif operator == "or" && (isValid || successful)
|
203
|
+
successful = true
|
204
|
+
else
|
205
|
+
successful = isValid
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end # @match.each do
|
210
|
+
|
211
|
+
# The 'match' setting is deprecated and optional. If not set,
|
212
|
+
# default to success.
|
213
|
+
successful = true if @match.nil?
|
214
|
+
|
215
|
+
@logger.debug? && @logger.debug("Email Did we match any alerts for event : ", :successful => successful)
|
216
|
+
|
217
|
+
if successful
|
218
|
+
# first add our custom field - matchName - so we can use it in the sprintf function
|
219
|
+
event["matchName"] = matchName unless matchName.empty?
|
220
|
+
@logger.debug? and @logger.debug("Creating mail with these settings : ", :via => @via, :options => @options, :from => @from, :to => @to, :cc => @cc, :subject => @subject, :body => @body, :content_type => @contenttype, :htmlbody => @htmlbody, :attachments => @attachments, :to => to, :to => to)
|
221
|
+
formatedSubject = event.sprintf(@subject)
|
222
|
+
formattedBody = event.sprintf(@body)
|
223
|
+
formattedHtmlBody = event.sprintf(@htmlbody)
|
224
|
+
# we have a match(s) - send email
|
225
|
+
mail = Mail.new
|
226
|
+
mail.from = event.sprintf(@from)
|
227
|
+
mail.to = event.sprintf(@to)
|
228
|
+
if @replyto
|
229
|
+
mail.reply_to = event.sprintf(@replyto)
|
230
|
+
end
|
231
|
+
mail.cc = event.sprintf(@cc)
|
232
|
+
mail.subject = formatedSubject
|
233
|
+
if @htmlbody.empty?
|
234
|
+
formattedBody.gsub!(/\\n/, "\n") # Take new line in the email
|
235
|
+
mail.body = formattedBody
|
236
|
+
else
|
237
|
+
mail.text_part = Mail::Part.new do
|
238
|
+
content_type "text/plain; charset=UTF-8"
|
239
|
+
formattedBody.gsub!(/\\n/, "\n") # Take new line in the email
|
240
|
+
body formattedBody
|
241
|
+
end
|
242
|
+
mail.html_part = Mail::Part.new do
|
243
|
+
content_type "text/html; charset=UTF-8"
|
244
|
+
body formattedHtmlBody
|
245
|
+
end
|
246
|
+
end
|
247
|
+
@attachments.each do |fileLocation|
|
248
|
+
mail.add_file(fileLocation)
|
249
|
+
end # end @attachments.each
|
250
|
+
@logger.debug? and @logger.debug("Sending mail with these values : ", :from => mail.from, :to => mail.to, :cc => mail.cc, :subject => mail.subject)
|
251
|
+
mail.deliver!
|
252
|
+
end # end if successful
|
253
|
+
end # def receive
|
254
|
+
|
255
|
+
|
256
|
+
private
|
257
|
+
def validateValue(eventFieldValue, value)
|
258
|
+
valid = false
|
259
|
+
# order of this if-else is important - please don't change it
|
260
|
+
if value.start_with?(">=")# greater than or equal
|
261
|
+
value.gsub!(">=","")
|
262
|
+
if eventFieldValue.to_i >= value.to_i
|
263
|
+
valid = true
|
264
|
+
end
|
265
|
+
elsif value.start_with?("<=")# less than or equal
|
266
|
+
value.gsub!("<=","")
|
267
|
+
if eventFieldValue.to_i <= value.to_i
|
268
|
+
valid = true
|
269
|
+
end
|
270
|
+
elsif value.start_with?(">")# greater than
|
271
|
+
value.gsub!(">","")
|
272
|
+
if eventFieldValue.to_i > value.to_i
|
273
|
+
valid = true
|
274
|
+
end
|
275
|
+
elsif value.start_with?("<")# less than
|
276
|
+
value.gsub!("<","")
|
277
|
+
if eventFieldValue.to_i < value.to_i
|
278
|
+
valid = true
|
279
|
+
end
|
280
|
+
elsif value.start_with?("*")# contains
|
281
|
+
value.gsub!("*","")
|
282
|
+
if eventFieldValue.include?(value)
|
283
|
+
valid = true
|
284
|
+
end
|
285
|
+
elsif value.start_with?("!*")# does not contain
|
286
|
+
value.gsub!("!*","")
|
287
|
+
if !eventFieldValue.include?(value)
|
288
|
+
valid = true
|
289
|
+
end
|
290
|
+
elsif value.start_with?("!")# not equal
|
291
|
+
value.gsub!("!","")
|
292
|
+
if eventFieldValue != value
|
293
|
+
valid = true
|
294
|
+
end
|
295
|
+
else # default equal
|
296
|
+
if eventFieldValue == value
|
297
|
+
valid = true
|
298
|
+
end
|
299
|
+
end
|
300
|
+
return valid
|
301
|
+
end # end validateValue()
|
302
|
+
|
303
|
+
end # class LogStash::Outputs::Email
|
@@ -0,0 +1,28 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
|
3
|
+
s.name = 'logstash-output-email'
|
4
|
+
s.version = '0.1.0'
|
5
|
+
s.licenses = ['Apache License (2.0)']
|
6
|
+
s.summary = "Send email when an output is received."
|
7
|
+
s.description = "Send email when an output is received."
|
8
|
+
s.authors = ["Elasticsearch"]
|
9
|
+
s.email = 'richard.pijnenburg@elasticsearch.com'
|
10
|
+
s.homepage = "http://logstash.net/"
|
11
|
+
s.require_paths = ["lib"]
|
12
|
+
|
13
|
+
# Files
|
14
|
+
s.files = `git ls-files`.split($\)+::Dir.glob('vendor/*')
|
15
|
+
|
16
|
+
# Tests
|
17
|
+
s.test_files = s.files.grep(%r{^(test|spec|features)/})
|
18
|
+
|
19
|
+
# Special flag to let us know this is actually a logstash plugin
|
20
|
+
s.metadata = { "logstash_plugin" => "true", "group" => "output" }
|
21
|
+
|
22
|
+
# Gem dependencies
|
23
|
+
s.add_runtime_dependency 'logstash', '>= 1.4.0', '< 2.0.0'
|
24
|
+
|
25
|
+
s.add_runtime_dependency 'mail'
|
26
|
+
|
27
|
+
end
|
28
|
+
|
@@ -0,0 +1,9 @@
|
|
1
|
+
require "gem_publisher"
|
2
|
+
|
3
|
+
desc "Publish gem to RubyGems.org"
|
4
|
+
task :publish_gem do |t|
|
5
|
+
gem_file = Dir.glob(File.expand_path('../*.gemspec',File.dirname(__FILE__))).first
|
6
|
+
gem = GemPublisher.publish_if_updated(gem_file, :rubygems)
|
7
|
+
puts "Published #{gem}" if gem
|
8
|
+
end
|
9
|
+
|
data/rakelib/vendor.rake
ADDED
@@ -0,0 +1,169 @@
|
|
1
|
+
require "net/http"
|
2
|
+
require "uri"
|
3
|
+
require "digest/sha1"
|
4
|
+
|
5
|
+
def vendor(*args)
|
6
|
+
return File.join("vendor", *args)
|
7
|
+
end
|
8
|
+
|
9
|
+
directory "vendor/" => ["vendor"] do |task, args|
|
10
|
+
mkdir task.name
|
11
|
+
end
|
12
|
+
|
13
|
+
def fetch(url, sha1, output)
|
14
|
+
|
15
|
+
puts "Downloading #{url}"
|
16
|
+
actual_sha1 = download(url, output)
|
17
|
+
|
18
|
+
if actual_sha1 != sha1
|
19
|
+
fail "SHA1 does not match (expected '#{sha1}' but got '#{actual_sha1}')"
|
20
|
+
end
|
21
|
+
end # def fetch
|
22
|
+
|
23
|
+
def file_fetch(url, sha1)
|
24
|
+
filename = File.basename( URI(url).path )
|
25
|
+
output = "vendor/#{filename}"
|
26
|
+
task output => [ "vendor/" ] do
|
27
|
+
begin
|
28
|
+
actual_sha1 = file_sha1(output)
|
29
|
+
if actual_sha1 != sha1
|
30
|
+
fetch(url, sha1, output)
|
31
|
+
end
|
32
|
+
rescue Errno::ENOENT
|
33
|
+
fetch(url, sha1, output)
|
34
|
+
end
|
35
|
+
end.invoke
|
36
|
+
|
37
|
+
return output
|
38
|
+
end
|
39
|
+
|
40
|
+
def file_sha1(path)
|
41
|
+
digest = Digest::SHA1.new
|
42
|
+
fd = File.new(path, "r")
|
43
|
+
while true
|
44
|
+
begin
|
45
|
+
digest << fd.sysread(16384)
|
46
|
+
rescue EOFError
|
47
|
+
break
|
48
|
+
end
|
49
|
+
end
|
50
|
+
return digest.hexdigest
|
51
|
+
ensure
|
52
|
+
fd.close if fd
|
53
|
+
end
|
54
|
+
|
55
|
+
def download(url, output)
|
56
|
+
uri = URI(url)
|
57
|
+
digest = Digest::SHA1.new
|
58
|
+
tmp = "#{output}.tmp"
|
59
|
+
Net::HTTP.start(uri.host, uri.port, :use_ssl => (uri.scheme == "https")) do |http|
|
60
|
+
request = Net::HTTP::Get.new(uri.path)
|
61
|
+
http.request(request) do |response|
|
62
|
+
fail "HTTP fetch failed for #{url}. #{response}" if [200, 301].include?(response.code)
|
63
|
+
size = (response["content-length"].to_i || -1).to_f
|
64
|
+
count = 0
|
65
|
+
File.open(tmp, "w") do |fd|
|
66
|
+
response.read_body do |chunk|
|
67
|
+
fd.write(chunk)
|
68
|
+
digest << chunk
|
69
|
+
if size > 0 && $stdout.tty?
|
70
|
+
count += chunk.bytesize
|
71
|
+
$stdout.write(sprintf("\r%0.2f%%", count/size * 100))
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
$stdout.write("\r \r") if $stdout.tty?
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
File.rename(tmp, output)
|
80
|
+
|
81
|
+
return digest.hexdigest
|
82
|
+
rescue SocketError => e
|
83
|
+
puts "Failure while downloading #{url}: #{e}"
|
84
|
+
raise
|
85
|
+
ensure
|
86
|
+
File.unlink(tmp) if File.exist?(tmp)
|
87
|
+
end # def download
|
88
|
+
|
89
|
+
def untar(tarball, &block)
|
90
|
+
require "archive/tar/minitar"
|
91
|
+
tgz = Zlib::GzipReader.new(File.open(tarball))
|
92
|
+
# Pull out typesdb
|
93
|
+
tar = Archive::Tar::Minitar::Input.open(tgz)
|
94
|
+
tar.each do |entry|
|
95
|
+
path = block.call(entry)
|
96
|
+
next if path.nil?
|
97
|
+
parent = File.dirname(path)
|
98
|
+
|
99
|
+
mkdir_p parent unless File.directory?(parent)
|
100
|
+
|
101
|
+
# Skip this file if the output file is the same size
|
102
|
+
if entry.directory?
|
103
|
+
mkdir path unless File.directory?(path)
|
104
|
+
else
|
105
|
+
entry_mode = entry.instance_eval { @mode } & 0777
|
106
|
+
if File.exists?(path)
|
107
|
+
stat = File.stat(path)
|
108
|
+
# TODO(sissel): Submit a patch to archive-tar-minitar upstream to
|
109
|
+
# expose headers in the entry.
|
110
|
+
entry_size = entry.instance_eval { @size }
|
111
|
+
# If file sizes are same, skip writing.
|
112
|
+
next if stat.size == entry_size && (stat.mode & 0777) == entry_mode
|
113
|
+
end
|
114
|
+
puts "Extracting #{entry.full_name} from #{tarball} #{entry_mode.to_s(8)}"
|
115
|
+
File.open(path, "w") do |fd|
|
116
|
+
# eof? check lets us skip empty files. Necessary because the API provided by
|
117
|
+
# Archive::Tar::Minitar::Reader::EntryStream only mostly acts like an
|
118
|
+
# IO object. Something about empty files in this EntryStream causes
|
119
|
+
# IO.copy_stream to throw "can't convert nil into String" on JRuby
|
120
|
+
# TODO(sissel): File a bug about this.
|
121
|
+
while !entry.eof?
|
122
|
+
chunk = entry.read(16384)
|
123
|
+
fd.write(chunk)
|
124
|
+
end
|
125
|
+
#IO.copy_stream(entry, fd)
|
126
|
+
end
|
127
|
+
File.chmod(entry_mode, path)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
tar.close
|
131
|
+
File.unlink(tarball) if File.file?(tarball)
|
132
|
+
end # def untar
|
133
|
+
|
134
|
+
def ungz(file)
|
135
|
+
|
136
|
+
outpath = file.gsub('.gz', '')
|
137
|
+
tgz = Zlib::GzipReader.new(File.open(file))
|
138
|
+
begin
|
139
|
+
File.open(outpath, "w") do |out|
|
140
|
+
IO::copy_stream(tgz, out)
|
141
|
+
end
|
142
|
+
File.unlink(file)
|
143
|
+
rescue
|
144
|
+
File.unlink(outpath) if File.file?(outpath)
|
145
|
+
raise
|
146
|
+
end
|
147
|
+
tgz.close
|
148
|
+
end
|
149
|
+
|
150
|
+
desc "Process any vendor files required for this plugin"
|
151
|
+
task "vendor" do |task, args|
|
152
|
+
|
153
|
+
@files.each do |file|
|
154
|
+
download = file_fetch(file['url'], file['sha1'])
|
155
|
+
if download =~ /.tar.gz/
|
156
|
+
prefix = download.gsub('.tar.gz', '').gsub('vendor/', '')
|
157
|
+
untar(download) do |entry|
|
158
|
+
if !file['files'].nil?
|
159
|
+
next unless file['files'].include?(entry.full_name.gsub(prefix, ''))
|
160
|
+
out = entry.full_name.split("/").last
|
161
|
+
end
|
162
|
+
File.join('vendor', out)
|
163
|
+
end
|
164
|
+
elsif download =~ /.gz/
|
165
|
+
ungz(download)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
end
|
@@ -0,0 +1,173 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "rumbster"
|
3
|
+
require "message_observers"
|
4
|
+
|
5
|
+
describe "outputs/email", :broken => true do
|
6
|
+
|
7
|
+
|
8
|
+
@@port=2525
|
9
|
+
let (:rumbster) { Rumbster.new(@@port) }
|
10
|
+
let (:message_observer) { MailMessageObserver.new }
|
11
|
+
|
12
|
+
before :each do
|
13
|
+
rumbster.add_observer message_observer
|
14
|
+
rumbster.start
|
15
|
+
end
|
16
|
+
|
17
|
+
after :each do
|
18
|
+
rumbster.stop
|
19
|
+
end
|
20
|
+
|
21
|
+
describe "use a list of email as mail.to (LOGSTASH-827)" do
|
22
|
+
config <<-CONFIG
|
23
|
+
input {
|
24
|
+
generator {
|
25
|
+
message => "hello world"
|
26
|
+
count => 1
|
27
|
+
type => "generator"
|
28
|
+
}
|
29
|
+
}
|
30
|
+
filter {
|
31
|
+
noop {
|
32
|
+
add_field => ["dummy_match", "ok"]
|
33
|
+
}
|
34
|
+
}
|
35
|
+
output{
|
36
|
+
email {
|
37
|
+
to => "email1@host, email2@host"
|
38
|
+
match => ["mymatch", "dummy_match,ok"]
|
39
|
+
options => ["port", #{@@port}]
|
40
|
+
}
|
41
|
+
}
|
42
|
+
CONFIG
|
43
|
+
|
44
|
+
agent do
|
45
|
+
insist {message_observer.messages.size} == 1
|
46
|
+
insist {message_observer.messages[0].to} == ["email1@host", "email2@host"]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe "use an array of email as mail.to (LOGSTASH-827)" do
|
51
|
+
config <<-CONFIG
|
52
|
+
input {
|
53
|
+
generator {
|
54
|
+
message => "hello world"
|
55
|
+
count => 1
|
56
|
+
type => "generator"
|
57
|
+
}
|
58
|
+
}
|
59
|
+
filter {
|
60
|
+
noop {
|
61
|
+
add_field => ["dummy_match", "ok"]
|
62
|
+
add_field => ["to_addr", "email1@host"]
|
63
|
+
add_field => ["to_addr", "email2@host"]
|
64
|
+
}
|
65
|
+
}
|
66
|
+
output{
|
67
|
+
email {
|
68
|
+
to => "%{to_addr}"
|
69
|
+
match => ["mymatch", "dummy_match,ok"]
|
70
|
+
options => ["port", #{@@port}]
|
71
|
+
}
|
72
|
+
}
|
73
|
+
CONFIG
|
74
|
+
|
75
|
+
agent do
|
76
|
+
insist {message_observer.messages.size} == 1
|
77
|
+
insist {message_observer.messages[0].to} == ["email1@host", "email2@host"]
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
describe "multi-lined text body (LOGSTASH-841)" do
|
82
|
+
config <<-CONFIG
|
83
|
+
input {
|
84
|
+
generator {
|
85
|
+
message => "hello world"
|
86
|
+
count => 1
|
87
|
+
type => "generator"
|
88
|
+
}
|
89
|
+
}
|
90
|
+
filter {
|
91
|
+
noop {
|
92
|
+
add_field => ["dummy_match", "ok"]
|
93
|
+
}
|
94
|
+
}
|
95
|
+
output{
|
96
|
+
email {
|
97
|
+
to => "me@host"
|
98
|
+
subject => "Hello World"
|
99
|
+
body => "Line1\\nLine2\\nLine3"
|
100
|
+
match => ["mymatch", "dummy_match,*"]
|
101
|
+
options => ["port", #{@@port}]
|
102
|
+
}
|
103
|
+
}
|
104
|
+
CONFIG
|
105
|
+
|
106
|
+
agent do
|
107
|
+
insist {message_observer.messages.size} == 1
|
108
|
+
insist {message_observer.messages[0].subject} == "Hello World"
|
109
|
+
insist {message_observer.messages[0].body.raw_source} == "Line1\r\nLine2\r\nLine3"
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
describe "use nil authenticationType (LOGSTASH-559)" do
|
114
|
+
config <<-CONFIG
|
115
|
+
input {
|
116
|
+
generator {
|
117
|
+
message => "hello world"
|
118
|
+
count => 1
|
119
|
+
type => "generator"
|
120
|
+
}
|
121
|
+
}
|
122
|
+
filter {
|
123
|
+
noop {
|
124
|
+
add_field => ["dummy_match", "ok"]
|
125
|
+
}
|
126
|
+
}
|
127
|
+
output{
|
128
|
+
email {
|
129
|
+
to => "me@host"
|
130
|
+
subject => "Hello World"
|
131
|
+
body => "Line1\\nLine2\\nLine3"
|
132
|
+
match => ["mymatch", "dummy_match,*"]
|
133
|
+
options => ["port", #{@@port}, "authenticationType", "nil"]
|
134
|
+
}
|
135
|
+
}
|
136
|
+
CONFIG
|
137
|
+
|
138
|
+
agent do
|
139
|
+
insist {message_observer.messages.size} == 1
|
140
|
+
insist {message_observer.messages[0].subject} == "Hello World"
|
141
|
+
insist {message_observer.messages[0].body.raw_source} == "Line1\r\nLine2\r\nLine3"
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
describe "match on source and message (LOGSTASH-826)" do
|
146
|
+
config <<-CONFIG
|
147
|
+
input {
|
148
|
+
generator {
|
149
|
+
message => "hello world"
|
150
|
+
count => 1
|
151
|
+
type => "generator"
|
152
|
+
}
|
153
|
+
}
|
154
|
+
output{
|
155
|
+
email {
|
156
|
+
to => "me@host"
|
157
|
+
subject => "Hello World"
|
158
|
+
body => "Mail body"
|
159
|
+
match => ["messageAndSourceMatch", "message,*hello,,and,source,*generator"]
|
160
|
+
options => ["port", #{@@port}, "authenticationType", "nil"]
|
161
|
+
}
|
162
|
+
}
|
163
|
+
CONFIG
|
164
|
+
|
165
|
+
agent do
|
166
|
+
insist {message_observer.messages.size} == 1
|
167
|
+
insist {message_observer.messages[0].subject} == "Hello World"
|
168
|
+
insist {message_observer.messages[0].body.raw_source} == "Mail body"
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
|
metadata
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: logstash-output-email
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Elasticsearch
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-11-03 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: logstash
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ! '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 1.4.0
|
20
|
+
- - <
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 2.0.0
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 1.4.0
|
30
|
+
- - <
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 2.0.0
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: mail
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ! '>='
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '0'
|
40
|
+
type: :runtime
|
41
|
+
prerelease: false
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - ! '>='
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
47
|
+
description: Send email when an output is received.
|
48
|
+
email: richard.pijnenburg@elasticsearch.com
|
49
|
+
executables: []
|
50
|
+
extensions: []
|
51
|
+
extra_rdoc_files: []
|
52
|
+
files:
|
53
|
+
- .gitignore
|
54
|
+
- Gemfile
|
55
|
+
- Rakefile
|
56
|
+
- lib/logstash/outputs/email.rb
|
57
|
+
- logstash-output-email.gemspec
|
58
|
+
- rakelib/publish.rake
|
59
|
+
- rakelib/vendor.rake
|
60
|
+
- spec/outputs/email_spec.rb
|
61
|
+
homepage: http://logstash.net/
|
62
|
+
licenses:
|
63
|
+
- Apache License (2.0)
|
64
|
+
metadata:
|
65
|
+
logstash_plugin: 'true'
|
66
|
+
group: output
|
67
|
+
post_install_message:
|
68
|
+
rdoc_options: []
|
69
|
+
require_paths:
|
70
|
+
- lib
|
71
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ! '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
77
|
+
requirements:
|
78
|
+
- - ! '>='
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: '0'
|
81
|
+
requirements: []
|
82
|
+
rubyforge_project:
|
83
|
+
rubygems_version: 2.4.1
|
84
|
+
signing_key:
|
85
|
+
specification_version: 4
|
86
|
+
summary: Send email when an output is received.
|
87
|
+
test_files:
|
88
|
+
- spec/outputs/email_spec.rb
|