fluent-plugin-slack 0.4.0 → 0.5.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,107 @@
1
+ require 'uri'
2
+ require 'net/http'
3
+
4
+ module Fluent
5
+ module SlackClient
6
+ class Error < StandardError; end
7
+
8
+ # This slack client only supports posting message
9
+ class Base
10
+ attr_accessor :log, :debug_dev
11
+
12
+ DEFAULT_ENDPOINT = "https://slack.com/api/chat.postMessage"
13
+
14
+ def initialize(endpoint = nil)
15
+ # Configure Incoming Webhook endpoint instead of chat.postMessage API
16
+ @endpoint = URI.parse(endpoint) if endpoint
17
+ end
18
+
19
+ def endpoint
20
+ @endpoint ||= URI.parse(DEFAULT_ENDPOINT)
21
+ end
22
+
23
+ # Sends a message to a channel.
24
+ #
25
+ # @option params [channel] :channel
26
+ # Channel to send message to. Can be a public channel, private group or IM channel. Can be an encoded ID, or a name.
27
+ # @option params [Object] :text
28
+ # Text of the message to send. See below for an explanation of formatting.
29
+ # @option params [Object] :username
30
+ # Name of bot.
31
+ # @option params [Object] :parse
32
+ # Change how messages are treated. See below.
33
+ # @option params [Object] :link_names
34
+ # Find and link channel names and usernames.
35
+ # @option params [Object] :attachments
36
+ # Structured message attachments.
37
+ # @option params [Object] :unfurl_links
38
+ # Pass true to enable unfurling of primarily text-based content.
39
+ # @option params [Object] :unfurl_media
40
+ # Pass false to disable unfurling of media content.
41
+ # @option params [Object] :icon_url
42
+ # URL to an image to use as the icon for this message
43
+ # @option params [Object] :icon_emoji
44
+ # emoji to use as the icon for this message. Overrides `icon_url`.
45
+ # @see https://api.slack.com/methods/chat.postMessage
46
+ # @see https://github.com/slackhq/slack-api-docs/blob/master/methods/chat.postMessage.md
47
+ # @see https://github.com/slackhq/slack-api-docs/blob/master/methods/chat.postMessage.json
48
+ def post_message(params = {})
49
+ http = Net::HTTP.new(endpoint.host, endpoint.port)
50
+ http.use_ssl = (endpoint.scheme == 'https')
51
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
52
+ http.set_debug_output(debug_dev) if debug_dev
53
+
54
+ req = Net::HTTP::Post.new(endpoint.path)
55
+ req['Host'] = endpoint.host
56
+ req['Accept'] = 'application/json; charset=utf-8'
57
+ req['User-Agent'] = 'fluent-plugin-slack'
58
+ req.body = encode_body(params)
59
+
60
+ log.info { "out_slack: post #{params.dup.tap {|p| p[:token] = '[FILTERED]' if p[:token] }}" } if log
61
+ res = http.request(req)
62
+ response_check(res)
63
+ end
64
+
65
+ def encode_body(params)
66
+ raise NotImplementedError
67
+ end
68
+
69
+ def response_check(res)
70
+ if res.code != "200"
71
+ raise Error, "Slack.com - #{res.code} - #{res.body}"
72
+ end
73
+ end
74
+ end
75
+
76
+ class IncomingWebhook < Base
77
+ def encode_body(params = {})
78
+ params.to_json
79
+ end
80
+
81
+ def response_check(res)
82
+ super
83
+ unless res.body == 'ok'
84
+ raise Error, "Slack.com - #{res.code} - #{res.body}"
85
+ end
86
+ end
87
+ end
88
+
89
+ class WebApi < Base
90
+ def encode_body(params = {})
91
+ body = params.dup
92
+ if params[:attachments]
93
+ body[:attachments] = params[:attachments].to_json
94
+ end
95
+ URI.encode_www_form(body)
96
+ end
97
+
98
+ def response_check(res)
99
+ super
100
+ params = JSON.parse(res.body)
101
+ unless params['ok']
102
+ raise Error, "Slack.com - #{res.code} - #{res.body}"
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,259 @@
1
+ require_relative '../test_helper'
2
+ require 'fluent/plugin/out_slack'
3
+ require 'time'
4
+
5
+ class SlackOutputTest < Test::Unit::TestCase
6
+
7
+ def setup
8
+ super
9
+ Fluent::Test.setup
10
+ end
11
+
12
+ CONFIG = %[
13
+ channel channel
14
+ token XXX-XXX-XXX
15
+ ]
16
+
17
+ def create_driver(conf = CONFIG)
18
+ Fluent::Test::BufferedOutputTestDriver.new(Fluent::SlackOutput).configure(conf)
19
+ end
20
+
21
+ # old version compatibility with v0.4.0"
22
+ def test_old_config
23
+ # default check
24
+ d = create_driver
25
+ assert_equal true, d.instance.localtime
26
+ assert_equal 'fluentd', d.instance.username
27
+ assert_equal 'good', d.instance.color
28
+ assert_equal ':question:', d.instance.icon_emoji
29
+
30
+ # incoming webhook endpoint was changed. api_key option should be ignored
31
+ assert_nothing_raised do
32
+ create_driver(CONFIG + %[api_key testtoken])
33
+ end
34
+
35
+ # incoming webhook endpoint was changed. team option should be ignored
36
+ assert_nothing_raised do
37
+ create_driver(CONFIG + %[team sowasowa])
38
+ end
39
+
40
+ # rtm? it was not calling `rtm.start`. rtm option was removed and should be ignored
41
+ assert_nothing_raised do
42
+ create_driver(CONFIG + %[rtm true])
43
+ end
44
+
45
+ # channel should be URI.unescape-ed
46
+ d = create_driver(CONFIG + %[channel %23test])
47
+ assert_equal '#test', d.instance.channel
48
+
49
+ # timezone should work
50
+ d = create_driver(CONFIG + %[timezone Asia/Tokyo])
51
+ assert_equal 'Asia/Tokyo', d.instance.timezone
52
+ end
53
+
54
+ def test_configure
55
+ d = create_driver(%[
56
+ channel channel
57
+ time_format %Y/%m/%d %H:%M:%S
58
+ username username
59
+ color bad
60
+ icon_emoji :ghost:
61
+ token XX-XX-XX
62
+ title slack notice!
63
+ message %s
64
+ message_keys message
65
+ ])
66
+ assert_equal '#channel', d.instance.channel
67
+ assert_equal '%Y/%m/%d %H:%M:%S', d.instance.time_format
68
+ assert_equal 'username', d.instance.username
69
+ assert_equal 'bad', d.instance.color
70
+ assert_equal ':ghost:', d.instance.icon_emoji
71
+ assert_equal 'XX-XX-XX', d.instance.token
72
+ assert_equal '%s', d.instance.message
73
+ assert_equal ['message'], d.instance.message_keys
74
+
75
+ assert_raise(Fluent::ConfigError) do
76
+ create_driver(CONFIG + %[title %s %s\ntitle_keys foo])
77
+ end
78
+
79
+ assert_raise(Fluent::ConfigError) do
80
+ create_driver(CONFIG + %[message %s %s\nmessage_keys foo])
81
+ end
82
+
83
+ assert_raise(Fluent::ConfigError) do
84
+ create_driver(CONFIG + %[channel %s %s\nchannel_keys foo])
85
+ end
86
+ end
87
+
88
+ def test_timezone_configure
89
+ time = Time.parse("2014-01-01 22:00:00 UTC").to_i
90
+
91
+ d = create_driver(CONFIG + %[localtime])
92
+ with_timezone('Asia/Tokyo') do
93
+ assert_equal true, d.instance.localtime
94
+ assert_equal "07:00:00", d.instance.timef.format(time)
95
+ end
96
+
97
+ d = create_driver(CONFIG + %[utc])
98
+ with_timezone('Asia/Tokyo') do
99
+ assert_equal false, d.instance.localtime
100
+ assert_equal "22:00:00", d.instance.timef.format(time)
101
+ end
102
+
103
+ d = create_driver(CONFIG + %[timezone Asia/Taipei])
104
+ with_timezone('Asia/Tokyo') do
105
+ assert_equal "Asia/Taipei", d.instance.timezone
106
+ assert_equal "06:00:00", d.instance.timef.format(time)
107
+ end
108
+ end
109
+
110
+ def test_time_format_configure
111
+ time = Time.parse("2014-01-01 22:00:00 UTC").to_i
112
+
113
+ d = create_driver(CONFIG + %[time_format %Y/%m/%d %H:%M:%S])
114
+ with_timezone('Asia/Tokyo') do
115
+ assert_equal "2014/01/02 07:00:00", d.instance.timef.format(time)
116
+ end
117
+ end
118
+
119
+ def test_buffer_configure
120
+ assert_nothing_raised do
121
+ create_driver(CONFIG + %[buffer_type file\nbuffer_path tmp/])
122
+ end
123
+ end
124
+
125
+ def test_default_incoming_webhook
126
+ d = create_driver(%[
127
+ channel channel
128
+ webhook_url https://hooks.slack.com/services/XXX/XXX/XXX
129
+ ])
130
+ time = Time.parse("2014-01-01 22:00:00 UTC").to_i
131
+ d.tag = 'test'
132
+ mock(d.instance.slack).post_message(
133
+ channel: '#channel',
134
+ username: 'fluentd',
135
+ icon_emoji: ':question:',
136
+ attachments: [{
137
+ color: 'good',
138
+ fallback: 'test',
139
+ fields: [{
140
+ title: 'test',
141
+ value: "[07:00:00] sowawa1\n[07:00:00] sowawa2\n",
142
+ }],
143
+ }]
144
+ )
145
+ with_timezone('Asia/Tokyo') do
146
+ d.emit({message: 'sowawa1'}, time)
147
+ d.emit({message: 'sowawa2'}, time)
148
+ d.run
149
+ end
150
+ end
151
+
152
+ def test_default_slack_api
153
+ d = create_driver(%[
154
+ channel channel
155
+ token XX-XX-XX
156
+ ])
157
+ time = Time.parse("2014-01-01 22:00:00 UTC").to_i
158
+ d.tag = 'test'
159
+ mock(d.instance.slack).post_message(
160
+ token: 'XX-XX-XX',
161
+ channel: '#channel',
162
+ username: 'fluentd',
163
+ icon_emoji: ':question:',
164
+ attachments: [{
165
+ color: 'good',
166
+ fallback: "sowawa1\nsowawa2\n",
167
+ text: "sowawa1\nsowawa2\n",
168
+ }]
169
+ )
170
+ with_timezone('Asia/Tokyo') do
171
+ d.emit({message: 'sowawa1'}, time)
172
+ d.emit({message: 'sowawa2'}, time)
173
+ d.run
174
+ end
175
+ end
176
+
177
+ def test_title_keys
178
+ d = create_driver(CONFIG + %[title %s\ntitle_keys tag])
179
+ time = Time.parse("2014-01-01 22:00:00 UTC").to_i
180
+ d.tag = 'test'
181
+ # attachments field should be changed to show the title
182
+ mock(d.instance.slack).post_message(
183
+ token: 'XXX-XXX-XXX',
184
+ channel: '#channel',
185
+ username: 'fluentd',
186
+ icon_emoji: ':question:',
187
+ attachments: [{
188
+ color: 'good',
189
+ fallback: d.tag,
190
+ fields: [
191
+ {
192
+ title: d.tag,
193
+ value: "sowawa1\nsowawa2\n",
194
+ }
195
+ ]
196
+ }]
197
+ )
198
+ with_timezone('Asia/Tokyo') do
199
+ d.emit({message: 'sowawa1'}, time)
200
+ d.emit({message: 'sowawa2'}, time)
201
+ d.run
202
+ end
203
+ end
204
+
205
+ def test_message_keys
206
+ d = create_driver(CONFIG + %[message %s %s\nmessage_keys tag,message])
207
+ time = Time.parse("2014-01-01 22:00:00 UTC").to_i
208
+ d.tag = 'test'
209
+ mock(d.instance.slack).post_message(
210
+ token: 'XXX-XXX-XXX',
211
+ channel: '#channel',
212
+ username: 'fluentd',
213
+ icon_emoji: ':question:',
214
+ attachments: [{
215
+ color: 'good',
216
+ fallback: "test sowawa1\ntest sowawa2\n",
217
+ text: "test sowawa1\ntest sowawa2\n",
218
+ }]
219
+ )
220
+ with_timezone('Asia/Tokyo') do
221
+ d.emit({message: 'sowawa1'}, time)
222
+ d.emit({message: 'sowawa2'}, time)
223
+ d.run
224
+ end
225
+ end
226
+
227
+ def test_channel_keys
228
+ d = create_driver(CONFIG + %[channel %s\nchannel_keys channel])
229
+ time = Time.parse("2014-01-01 22:00:00 UTC").to_i
230
+ d.tag = 'test'
231
+ mock(d.instance.slack).post_message(
232
+ token: 'XXX-XXX-XXX',
233
+ channel: '#channel1',
234
+ username: 'fluentd',
235
+ icon_emoji: ':question:',
236
+ attachments: [{
237
+ color: 'good',
238
+ fallback: "sowawa1\n",
239
+ text: "sowawa1\n",
240
+ }]
241
+ )
242
+ mock(d.instance.slack).post_message(
243
+ token: 'XXX-XXX-XXX',
244
+ channel: '#channel2',
245
+ username: 'fluentd',
246
+ icon_emoji: ':question:',
247
+ attachments: [{
248
+ color: 'good',
249
+ fallback: "sowawa2\n",
250
+ text: "sowawa2\n",
251
+ }]
252
+ )
253
+ with_timezone('Asia/Tokyo') do
254
+ d.emit({message: 'sowawa1', channel: 'channel1'}, time)
255
+ d.emit({message: 'sowawa2', channel: 'channel2'}, time)
256
+ d.run
257
+ end
258
+ end
259
+ end
@@ -0,0 +1,73 @@
1
+ require_relative '../test_helper'
2
+ require 'fluent/plugin/slack_client'
3
+ require 'time'
4
+ require 'dotenv'
5
+
6
+ # HOW TO RUN
7
+ #
8
+ # Create .env file with contents as:
9
+ #
10
+ # WEBHOOK_URL=https://hooks.slack.com/services/XXXX/YYYY/ZZZZ
11
+ # TOKEN=XXXXX
12
+ #
13
+ Dotenv.load
14
+ if ENV['WEBHOOK_URL'] and ENV['TOKEN']
15
+ class SlackClientTest < Test::Unit::TestCase
16
+ def setup
17
+ super
18
+ @incoming_webhook = Fluent::SlackClient::IncomingWebhook.new(ENV['WEBHOOK_URL'])
19
+ @api = Fluent::SlackClient::WebApi.new
20
+ end
21
+
22
+ def token(client)
23
+ client == @api ? {token: ENV['TOKEN']} : {}
24
+ end
25
+
26
+ def test_text
27
+ [@incoming_webhook, @api].each do |slack|
28
+ assert_nothing_raised do
29
+ slack.post_message(
30
+ {
31
+ channel: '#general',
32
+ username: 'fluentd',
33
+ icon_emoji: ':question:',
34
+ attachments: [{
35
+ color: 'good',
36
+ fallback: "sowawa1\nsowawa2\n",
37
+ text: "sowawa1\nsowawa2\n",
38
+ }]
39
+ }.merge(token(slack))
40
+ )
41
+ end
42
+ end
43
+ end
44
+
45
+ def test_fields
46
+ [@incoming_webhook, @api].each do |slack|
47
+ assert_nothing_raised do
48
+ slack.post_message(
49
+ {
50
+ channel: '#general',
51
+ username: 'fluentd',
52
+ icon_emoji: ':question:',
53
+ attachments: [{
54
+ color: 'good',
55
+ fallback: 'test1 test2',
56
+ fields: [
57
+ {
58
+ title: 'test1',
59
+ value: "[07:00:00] sowawa1\n[07:00:00] sowawa2\n",
60
+ },
61
+ {
62
+ title: 'test2',
63
+ value: "[07:00:00] sowawa1\n[07:00:00] sowawa2\n",
64
+ },
65
+ ],
66
+ }]
67
+ }.merge(token(slack))
68
+ )
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -23,5 +23,11 @@ unless ENV.has_key?('VERBOSE')
23
23
  $log = nulllogger
24
24
  end
25
25
 
26
- require 'fluent/plugin/out_buffered_slack'
26
+ def with_timezone(tz)
27
+ oldtz, ENV['TZ'] = ENV['TZ'], tz
28
+ yield
29
+ ensure
30
+ ENV['TZ'] = oldtz
31
+ end
32
+
27
33
  require 'rr'