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.
- checksums.yaml +4 -4
- data/.gitignore +3 -0
- data/README.md +87 -0
- data/VERSION +1 -1
- data/fluent-plugin-slack.gemspec +4 -3
- data/lib/fluent/plugin/out_buffered_slack.rb +169 -88
- data/lib/fluent/plugin/out_slack.rb +199 -0
- data/lib/fluent/plugin/slack_client.rb +107 -0
- data/test/plugin/test_out_slack.rb +259 -0
- data/test/plugin/test_slack_client.rb +73 -0
- data/test/test_helper.rb +7 -1
- metadata +47 -31
- data/README.rdoc +0 -46
- data/test/plugin/test_out_buffered_slack.rb +0 -73
- data/test/plugin/test_out_buffered_slack_rtm.rb +0 -63
@@ -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
|
data/test/test_helper.rb
CHANGED