fluent-plugin-ikachan 0.1.0 → 0.2.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.
@@ -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.