fluent-plugin-ikachan 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,13 @@
1
+ Copyright (c) 2012- TAGOMORI Satoshi
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
data/README.md CHANGED
@@ -35,7 +35,26 @@ And then, configure out_ikachan::
35
35
  tag_key tag
36
36
  </match>
37
37
 
38
- You will got message like 'notice: alert.servicename [2012/05/10 18:51:59] alert message in attribute "msg"'.
38
+ You will get a notice message like `notice: alert.servicename [2012/05/10 18:51:59] alert message in attribute "msg"`.
39
+
40
+ In addition to notice, out_ikachan can send privmsg into specified channel by `privmesg_message` and `privmsg_out_keys`.
41
+
42
+ <match alert.**>
43
+ # ikachan host/port(default 4979)
44
+ host localhost
45
+ port 4979
46
+ # channel to notify (this means #morischan)
47
+ channel morischan
48
+ message notice: %s [%s] %s
49
+ out_keys tag,time,msg
50
+ privmsg_message [%s] morischan :D
51
+ privmsg_out_keys time
52
+ time_key time
53
+ time_format %Y/%m/%d %H:%M:%S
54
+ tag_key tag
55
+ </match>
56
+
57
+ At least one of (`message` + `out_keys`) or (`privmsg_message` + `privmsg_out_keys`) must be specified.
39
58
 
40
59
  ## TODO
41
60
 
@@ -44,7 +63,8 @@ You will got message like 'notice: alert.servicename [2012/05/10 18:51:59] alert
44
63
 
45
64
  ## Copyright
46
65
 
47
- * Copyright
48
- * Copyright (c) 2012- TAGOMORI Satoshi (tagomoris)
66
+ * Copyright (c) 2012- TAGOMORI Satoshi (tagomoris)
67
+ * Contributed by:
68
+ * @sonots
49
69
  * License
50
70
  * Apache License, Version 2.0
@@ -1,7 +1,7 @@
1
1
  # -*- encoding: utf-8 -*-
2
2
  Gem::Specification.new do |gem|
3
3
  gem.name = "fluent-plugin-ikachan"
4
- gem.version = "0.1.0"
4
+ gem.version = "0.2.0"
5
5
  gem.authors = ["TAGOMORI Satoshi"]
6
6
  gem.email = ["tagomoris@gmail.com"]
7
7
  gem.summary = %q{output plugin for IRC-HTTP gateway 'ikachan'}
@@ -14,5 +14,6 @@ Gem::Specification.new do |gem|
14
14
  gem.require_paths = ["lib"]
15
15
 
16
16
  gem.add_development_dependency "fluentd"
17
+ gem.add_development_dependency "rake"
17
18
  gem.add_runtime_dependency "fluentd"
18
19
  end
@@ -4,8 +4,10 @@ class Fluent::IkachanOutput < Fluent::Output
4
4
  config_param :host, :string
5
5
  config_param :port, :integer, :default => 4979
6
6
  config_param :channel, :string
7
- config_param :message, :string
8
- config_param :out_keys, :string
7
+ config_param :message, :string, :default => nil
8
+ config_param :out_keys, :string, :default => ""
9
+ config_param :privmsg_message, :string, :default => nil
10
+ config_param :privmsg_out_keys, :string, :default => ""
9
11
  config_param :time_key, :string, :default => nil
10
12
  config_param :time_format, :string, :default => nil
11
13
  config_param :tag_key, :string, :default => 'tag'
@@ -22,19 +24,31 @@ class Fluent::IkachanOutput < Fluent::Output
22
24
  @channel = '#' + @channel
23
25
  @join_uri = URI.parse "http://#{@host}:#{@port}/join"
24
26
  @notice_uri = URI.parse "http://#{@host}:#{@port}/notice"
27
+ @privmsg_uri = URI.parse "http://#{@host}:#{@port}/privmsg"
25
28
 
26
- @out_keys = conf['out_keys'].split(',')
29
+ @out_keys = @out_keys.split(',')
30
+ @privmsg_out_keys = @privmsg_out_keys.split(',')
31
+
32
+ if @message.nil? and @privmsg_message.nil?
33
+ raise Fluent::ConfigError, "Either 'message' or 'privmsg_message' must be specifed."
34
+ end
27
35
 
28
36
  begin
29
- @message % (['1'] * @out_keys.length)
37
+ @message % (['1'] * @out_keys.length) if @message
30
38
  rescue ArgumentError
31
39
  raise Fluent::ConfigError, "string specifier '%s' and out_keys specification mismatch"
32
40
  end
33
41
 
42
+ begin
43
+ @privmsg_message % (['1'] * @privmsg_out_keys.length) if @privmsg_message
44
+ rescue ArgumentError
45
+ raise Fluent::ConfigError, "string specifier '%s' of privmsg_message and privmsg_out_keys specification mismatch"
46
+ end
47
+
34
48
  if @time_key
35
49
  if @time_format
36
50
  f = @time_format
37
- tf = Fluent::TimeFormatter.new(f, @localtime)
51
+ tf = Fluent::TimeFormatter.new(f, true) # IRC notification is formmatted as localtime only...
38
52
  @time_format_proc = tf.method(:format)
39
53
  @time_parse_proc = Proc.new {|str| Time.strptime(str, f).to_i }
40
54
  else
@@ -60,22 +74,11 @@ class Fluent::IkachanOutput < Fluent::Output
60
74
 
61
75
  def emit(tag, es, chain)
62
76
  messages = []
77
+ privmsg_messages = []
63
78
 
64
79
  es.each {|time,record|
65
- values = []
66
- last = @out_keys.length - 1
67
-
68
- values = @out_keys.map do |key|
69
- case key
70
- when @time_key
71
- @time_format_proc.call(time)
72
- when @tag_key
73
- tag
74
- else
75
- record[key].to_s
76
- end
77
- end
78
- messages.push (@message % values)
80
+ messages << evaluate_message(@message, @out_keys, tag, time, record) if @message
81
+ privmsg_messages << evaluate_message(@privmsg_message, @privmsg_out_keys, tag, time, record) if @privmsg_message
79
82
  }
80
83
 
81
84
  messages.each do |msg|
@@ -86,7 +89,35 @@ class Fluent::IkachanOutput < Fluent::Output
86
89
  end
87
90
  end
88
91
 
92
+ privmsg_messages.each do |msg|
93
+ begin
94
+ res = Net::HTTP.post_form(@privmsg_uri, {'channel' => @channel, 'message' => msg})
95
+ rescue
96
+ $log.warn "out_ikachan: failed to send privmsg to #{@host}:#{@port}, #{@channel}, message: #{msg}"
97
+ end
98
+ end
99
+
89
100
  chain.next
90
101
  end
91
102
 
103
+ private
104
+
105
+ def evaluate_message(message, out_keys, tag, time, record)
106
+ values = []
107
+ last = out_keys.length - 1
108
+
109
+ values = out_keys.map do |key|
110
+ case key
111
+ when @time_key
112
+ @time_format_proc.call(time)
113
+ when @tag_key
114
+ tag
115
+ else
116
+ record[key].to_s
117
+ end
118
+ end
119
+
120
+ message % values
121
+ end
122
+
92
123
  end
@@ -26,3 +26,27 @@ require 'fluent/plugin/out_ikachan'
26
26
 
27
27
  class Test::Unit::TestCase
28
28
  end
29
+
30
+ require 'webrick'
31
+
32
+ # to handle POST/PUT/DELETE ...
33
+ module WEBrick::HTTPServlet
34
+ class ProcHandler < AbstractServlet
35
+ alias do_POST do_GET
36
+ alias do_PUT do_GET
37
+ alias do_DELETE do_GET
38
+ end
39
+ end
40
+
41
+ def get_code(server, port, path, headers={})
42
+ require 'net/http'
43
+ Net::HTTP.start(server, port){|http|
44
+ http.get(path, headers).code
45
+ }
46
+ end
47
+ def get_content(server, port, path, headers={})
48
+ require 'net/http'
49
+ Net::HTTP.start(server, port){|http|
50
+ http.get(path, headers).body
51
+ }
52
+ end
@@ -1,7 +1,22 @@
1
1
  require 'helper'
2
+ require 'cgi'
2
3
 
3
4
  class IkachanOutputTest < Test::Unit::TestCase
5
+ IKACHAN_TEST_LISTEN_PORT = 4979
6
+
4
7
  CONFIG = %[
8
+ host localhost
9
+ channel morischan
10
+ message out_ikachan: %s [%s] %s
11
+ out_keys tag,time,msg
12
+ privmsg_message out_ikachan: %s [%s] %s
13
+ privmsg_out_keys tag,time,msg
14
+ time_key time
15
+ time_format %Y/%m/%d %H:%M:%S
16
+ tag_key tag
17
+ ]
18
+
19
+ CONFIG_NOTICE_ONLY = %[
5
20
  host localhost
6
21
  channel morischan
7
22
  message out_ikachan: %s [%s] %s
@@ -11,6 +26,16 @@ class IkachanOutputTest < Test::Unit::TestCase
11
26
  tag_key tag
12
27
  ]
13
28
 
29
+ CONFIG_PRIVMSG_ONLY = %[
30
+ host localhost
31
+ channel morischan
32
+ privmsg_message out_ikachan: %s [%s] %s
33
+ privmsg_out_keys tag,time,msg
34
+ time_key time
35
+ time_format %Y/%m/%d %H:%M:%S
36
+ tag_key tag
37
+ ]
38
+
14
39
  def create_driver(conf=CONFIG,tag='test')
15
40
  Fluent::Test::OutputTestDriver.new(Fluent::IkachanOutput, tag).configure(conf)
16
41
  end
@@ -18,16 +43,213 @@ class IkachanOutputTest < Test::Unit::TestCase
18
43
  def test_configure
19
44
  d = create_driver
20
45
  assert_equal '#morischan', d.instance.channel
46
+ d = create_driver(CONFIG_NOTICE_ONLY)
47
+ assert_equal '#morischan', d.instance.channel
48
+ d = create_driver(CONFIG_PRIVMSG_ONLY)
49
+ assert_equal '#morischan', d.instance.channel
21
50
  end
22
51
 
23
- def test_notice
24
- # To test this code, execute ikachan on your own host
52
+ # CONFIG = %[
53
+ # host localhost
54
+ # channel morischan
55
+ # message out_ikachan: %s [%s] %s
56
+ # out_keys tag,time,msg
57
+ # privmsg_message out_ikachan: %s [%s] %s
58
+ # privmsg_out_keys tag,time,msg
59
+ # time_key time
60
+ # time_format %Y/%m/%d %H:%M:%S
61
+ # tag_key tag
62
+ # ]
63
+ def test_notice_and_privmsg
25
64
  d = create_driver
26
- time = Time.now.to_i
65
+ t = Time.now
66
+ time = t.to_i
67
+ ts = t.strftime(d.instance.time_format)
27
68
  d.run do
28
- d.emit({'msg' => "message from fluentd out_ikachan: testing now"}, time)
29
- d.emit({'msg' => "message from fluentd out_ikachan: testing second line"}, time)
69
+ d.emit({'msg' => "both notice and privmsg message from fluentd out_ikachan: testing now"}, time)
70
+ d.emit({'msg' => "both notice and privmsg message from fluentd out_ikachan: testing second line"}, time)
30
71
  end
72
+
73
+ assert_equal 4, @posted.length
74
+
75
+ assert_equal 'notice', @posted[0][:method]
76
+ assert_equal '#morischan', @posted[0][:channel]
77
+ assert_equal "out_ikachan: test [#{ts}] both notice and privmsg message from fluentd out_ikachan: testing now", @posted[0][:message]
78
+
79
+ assert_equal 'privmsg', @posted[1][:method]
80
+ assert_equal '#morischan', @posted[1][:channel]
81
+ assert_equal "out_ikachan: test [#{ts}] both notice and privmsg message from fluentd out_ikachan: testing now", @posted[1][:message]
82
+
83
+ assert_equal 'notice', @posted[2][:method]
84
+ assert_equal '#morischan', @posted[2][:channel]
85
+ assert_equal "out_ikachan: test [#{ts}] both notice and privmsg message from fluentd out_ikachan: testing second line", @posted[2][:message]
86
+ assert_equal 'privmsg', @posted[3][:method]
87
+ assert_equal '#morischan', @posted[3][:channel]
88
+ assert_equal "out_ikachan: test [#{ts}] both notice and privmsg message from fluentd out_ikachan: testing second line", @posted[3][:message]
31
89
  end
32
90
 
91
+ # CONFIG_NOTICE_ONLY = %[
92
+ # host localhost
93
+ # channel morischan
94
+ # message out_ikachan: %s [%s] %s
95
+ # out_keys tag,time,msg
96
+ # time_key time
97
+ # time_format %Y/%m/%d %H:%M:%S
98
+ # tag_key tag
99
+ # ]
100
+ def test_notice_only
101
+ d = create_driver(CONFIG_NOTICE_ONLY)
102
+ t = Time.now
103
+ time = t.to_i
104
+ ts = t.strftime(d.instance.time_format)
105
+ d.run do
106
+ d.emit({'msg' => "notice message from fluentd out_ikachan: testing now"}, time)
107
+ d.emit({'msg' => "notice message from fluentd out_ikachan: testing second line"}, time)
108
+ end
109
+
110
+ assert_equal 2, @posted.length
111
+
112
+ assert_equal 'notice', @posted[0][:method]
113
+ assert_equal '#morischan', @posted[0][:channel]
114
+ assert_equal "out_ikachan: test [#{ts}] notice message from fluentd out_ikachan: testing now", @posted[0][:message]
115
+
116
+ assert_equal 'notice', @posted[1][:method]
117
+ assert_equal '#morischan', @posted[1][:channel]
118
+ assert_equal "out_ikachan: test [#{ts}] notice message from fluentd out_ikachan: testing second line", @posted[1][:message]
119
+ end
120
+
121
+ # CONFIG_PRIVMSG_ONLY = %[
122
+ # host localhost
123
+ # channel morischan
124
+ # privmsg_message out_ikachan: %s [%s] %s
125
+ # privmsg_out_keys tag,time,msg
126
+ # time_key time
127
+ # time_format %Y/%m/%d %H:%M:%S
128
+ # tag_key tag
129
+ # ]
130
+ def test_privmsg_only
131
+ d = create_driver(CONFIG_PRIVMSG_ONLY)
132
+ t = Time.now
133
+ time = t.to_i
134
+ ts = t.strftime(d.instance.time_format)
135
+ d.run do
136
+ d.emit({'msg' => "privmsg message from fluentd out_ikachan: testing now"}, time)
137
+ d.emit({'msg' => "privmsg message from fluentd out_ikachan: testing second line"}, time)
138
+ end
139
+
140
+ assert_equal 2, @posted.length
141
+
142
+ assert_equal 'privmsg', @posted[0][:method]
143
+ assert_equal '#morischan', @posted[0][:channel]
144
+ assert_equal "out_ikachan: test [#{ts}] privmsg message from fluentd out_ikachan: testing now", @posted[0][:message]
145
+
146
+ assert_equal 'privmsg', @posted[1][:method]
147
+ assert_equal '#morischan', @posted[1][:channel]
148
+ assert_equal "out_ikachan: test [#{ts}] privmsg message from fluentd out_ikachan: testing second line", @posted[1][:message]
149
+ end
150
+
151
+ # setup / teardown for servers
152
+ def setup
153
+ Fluent::Test.setup
154
+ @posted = []
155
+ @prohibited = 0
156
+ @auth = false
157
+ @dummy_server_thread = Thread.new do
158
+ srv = if ENV['VERBOSE']
159
+ WEBrick::HTTPServer.new({:BindAddress => '127.0.0.1', :Port => IKACHAN_TEST_LISTEN_PORT})
160
+ else
161
+ logger = WEBrick::Log.new('/dev/null', WEBrick::BasicLog::DEBUG)
162
+ WEBrick::HTTPServer.new({:BindAddress => '127.0.0.1', :Port => IKACHAN_TEST_LISTEN_PORT, :Logger => logger, :AccessLog => []})
163
+ end
164
+ begin
165
+ srv.mount_proc('/') { |req,res| # /join, /notice, /privmsg
166
+ # POST /join?channel=#channel&channel_keyword=keyword
167
+ # POST /notice?channel=#channel&message=your_message
168
+ # POST /privmsg?channel=#channel&message=your_message
169
+ unless req.request_method == 'POST'
170
+ res.status = 405
171
+ res.body = 'request method mismatch'
172
+ next
173
+ end
174
+ if @auth and req.header['authorization'][0] == 'Basic YWxpY2U6c2VjcmV0IQ==' # pattern of user='alice' passwd='secret!'
175
+ # ok, authorized
176
+ elsif @auth
177
+ res.status = 403
178
+ @prohibited += 1
179
+ next
180
+ else
181
+ # ok, authorization not required
182
+ end
183
+
184
+ if req.path == '/'
185
+ res.status = 200
186
+ next
187
+ end
188
+
189
+ req.path =~ /^\/(join|notice|privmsg)$/
190
+ method = $1
191
+ post_param = CGI.parse(req.body)
192
+
193
+ if method == 'join'
194
+ res.status = 200
195
+ next
196
+ end
197
+
198
+ @posted.push({ :method => method, :channel => post_param['channel'].first, :message => post_param['message'].first})
199
+ res.status = 200
200
+ }
201
+ srv.start
202
+ ensure
203
+ srv.shutdown
204
+ end
205
+ end
206
+ # to wait completion of dummy server.start()
207
+ require 'thread'
208
+ cv = ConditionVariable.new
209
+ watcher = Thread.new {
210
+ connected = false
211
+ while not connected
212
+ begin
213
+ get_content('localhost', IKACHAN_TEST_LISTEN_PORT, '/')
214
+ connected = true
215
+ rescue Errno::ECONNREFUSED
216
+ sleep 0.1
217
+ rescue StandardError => e
218
+ p e
219
+ sleep 0.1
220
+ end
221
+ end
222
+ cv.signal
223
+ }
224
+ mutex = Mutex.new
225
+ mutex.synchronize {
226
+ cv.wait(mutex)
227
+ }
228
+ end
229
+
230
+ def test_dummy_server
231
+ d = create_driver
232
+ host = d.instance.host
233
+ port = d.instance.port
234
+ client = Net::HTTP.start(host, port)
235
+
236
+ assert_equal '200', client.request_post('/', '').code
237
+ assert_equal '200', client.request_post('/join', 'channel=#test').code
238
+
239
+ assert_equal 0, @posted.size
240
+
241
+ assert_equal '200', client.request_post('/notice', 'channel=#test&message=NOW TESTING').code
242
+ assert_equal '200', client.request_post('/privmsg', 'channel=#test&message=NOW TESTING 2').code
243
+
244
+ assert_equal 2, @posted.size
245
+
246
+ assert_equal 'notice', @posted[0][:method]
247
+ assert_equal '#test', @posted[0][:channel]
248
+ assert_equal 'NOW TESTING', @posted[0][:message]
249
+ end
250
+
251
+ def teardown
252
+ @dummy_server_thread.kill
253
+ @dummy_server_thread.join
254
+ end
33
255
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fluent-plugin-ikachan
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-05-10 00:00:00.000000000 Z
12
+ date: 2013-02-08 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: fluentd
@@ -27,6 +27,22 @@ dependencies:
27
27
  - - ! '>='
28
28
  - !ruby/object:Gem::Version
29
29
  version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rake
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
30
46
  - !ruby/object:Gem::Dependency
31
47
  name: fluentd
32
48
  requirement: !ruby/object:Gem::Requirement
@@ -53,7 +69,7 @@ extra_rdoc_files: []
53
69
  files:
54
70
  - .gitignore
55
71
  - Gemfile
56
- - LICENSE
72
+ - LICENSE.txt
57
73
  - README.md
58
74
  - Rakefile
59
75
  - fluent-plugin-ikachan.gemspec
data/LICENSE DELETED
@@ -1,22 +0,0 @@
1
- Copyright (c) 2012 TAGOMORI Satoshi
2
-
3
- MIT License
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining
6
- a copy of this software and associated documentation files (the
7
- "Software"), to deal in the Software without restriction, including
8
- without limitation the rights to use, copy, modify, merge, publish,
9
- distribute, sublicense, and/or sell copies of the Software, and to
10
- permit persons to whom the Software is furnished to do so, subject to
11
- the following conditions:
12
-
13
- The above copyright notice and this permission notice shall be
14
- included in all copies or substantial portions of the Software.
15
-
16
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
- NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
- LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
- OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
- WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.