fluent-plugin-slack-stakater 0.6.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +8 -0
- data/.travis.yml +11 -0
- data/CHANGELOG.md +95 -0
- data/Gemfile +3 -0
- data/Gemfile.fluentd.0.12 +4 -0
- data/README.md +126 -0
- data/Rakefile +16 -0
- data/VERSION +1 -0
- data/example.conf +18 -0
- data/fluent-plugin-slack.gemspec +28 -0
- data/lib/fluent/plugin/out_buffered_slack.rb +1 -0
- data/lib/fluent/plugin/out_slack.rb +382 -0
- data/lib/fluent/plugin/slack_client.rb +289 -0
- data/lib/fluent/plugin/slack_client/error.rb +24 -0
- data/test.sh +2 -0
- data/test/plugin/test_out_slack.rb +566 -0
- data/test/plugin/test_slack_client.rb +282 -0
- data/test/test_helper.rb +32 -0
- metadata +176 -0
@@ -0,0 +1,289 @@
|
|
1
|
+
require 'uri'
|
2
|
+
require 'net/http'
|
3
|
+
require 'net/https'
|
4
|
+
require 'logger'
|
5
|
+
require_relative 'slack_client/error'
|
6
|
+
|
7
|
+
module Fluent
|
8
|
+
module SlackClient
|
9
|
+
# The base framework of slack client
|
10
|
+
class Base
|
11
|
+
attr_accessor :log, :debug_dev
|
12
|
+
attr_reader :endpoint, :https_proxy
|
13
|
+
|
14
|
+
# @param [String] endpoint
|
15
|
+
#
|
16
|
+
# (Incoming Webhook) required
|
17
|
+
# https://hooks.slack.com/services/XXX/XXX/XXX
|
18
|
+
#
|
19
|
+
# (Slackbot) required
|
20
|
+
# https://xxxx.slack.com/services/hooks/slackbot?token=XXXXX
|
21
|
+
#
|
22
|
+
# (Web API) optional and default to be
|
23
|
+
# https://slack.com/api/
|
24
|
+
#
|
25
|
+
# @param [String] https_proxy (optional)
|
26
|
+
#
|
27
|
+
# https://proxy.foo.bar:port
|
28
|
+
#
|
29
|
+
def initialize(endpoint = nil, https_proxy = nil)
|
30
|
+
self.endpoint = endpoint if endpoint
|
31
|
+
self.https_proxy = https_proxy if https_proxy
|
32
|
+
@log = Logger.new('/dev/null')
|
33
|
+
end
|
34
|
+
|
35
|
+
def endpoint=(endpoint)
|
36
|
+
@endpoint = URI.parse(endpoint)
|
37
|
+
end
|
38
|
+
|
39
|
+
def https_proxy=(https_proxy)
|
40
|
+
@https_proxy = URI.parse(https_proxy)
|
41
|
+
@proxy_class = Net::HTTP.Proxy(@https_proxy.host, @https_proxy.port)
|
42
|
+
end
|
43
|
+
|
44
|
+
def proxy_class
|
45
|
+
@proxy_class ||= Net::HTTP
|
46
|
+
end
|
47
|
+
|
48
|
+
def post(endpoint, params)
|
49
|
+
http = proxy_class.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.request_uri)
|
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
|
+
res = http.request(req)
|
61
|
+
response_check(res, params)
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def encode_body(params)
|
67
|
+
raise NotImplementedError
|
68
|
+
end
|
69
|
+
|
70
|
+
def response_check(res, params)
|
71
|
+
if res.code != "200"
|
72
|
+
raise Error.new(res, params)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def filter_params(params)
|
77
|
+
params.dup.tap {|p| p[:token] = '[FILTERED]' if p[:token] }
|
78
|
+
end
|
79
|
+
|
80
|
+
# Required to implement to use #with_channels_create
|
81
|
+
# channels.create API is available from only Slack Web API
|
82
|
+
def api
|
83
|
+
raise NotImplementedError
|
84
|
+
end
|
85
|
+
|
86
|
+
def with_channels_create(params = {}, opts = {})
|
87
|
+
retries = 1
|
88
|
+
begin
|
89
|
+
yield
|
90
|
+
rescue ChannelNotFoundError => e
|
91
|
+
if params[:token] and opts[:auto_channels_create]
|
92
|
+
log.warn "out_slack: channel \"#{params[:channel]}\" is not found. try to create the channel, and then retry to post the message."
|
93
|
+
api.channels_create({name: params[:channel], token: params[:token]})
|
94
|
+
retry if (retries -= 1) >= 0 # one time retry
|
95
|
+
else
|
96
|
+
raise e
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def to_json_with_scrub!(params)
|
102
|
+
retries = 1
|
103
|
+
begin
|
104
|
+
params.to_json
|
105
|
+
rescue Encoding::UndefinedConversionError => e
|
106
|
+
recursive_scrub!(params)
|
107
|
+
if (retries -= 1) >= 0 # one time retry
|
108
|
+
log.warn "out_slack: to_json `#{params}` failed. retry after scrub!. #{e.message}"
|
109
|
+
retry
|
110
|
+
else
|
111
|
+
raise e
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def recursive_scrub!(params)
|
117
|
+
case params
|
118
|
+
when Hash
|
119
|
+
params.each {|k, v| recursive_scrub!(v)}
|
120
|
+
when Array
|
121
|
+
params.each {|elm| recursive_scrub!(elm)}
|
122
|
+
when String
|
123
|
+
params.force_encoding(Encoding::UTF_8) if params.encoding == Encoding::ASCII_8BIT
|
124
|
+
params.scrub!('?') if params.respond_to?(:scrub!)
|
125
|
+
else
|
126
|
+
params
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# Slack client for Incoming Webhook
|
132
|
+
# https://api.slack.com/incoming-webhooks
|
133
|
+
class IncomingWebhook < Base
|
134
|
+
def initialize(endpoint, https_proxy = nil)
|
135
|
+
super
|
136
|
+
end
|
137
|
+
|
138
|
+
def post_message(params = {}, opts = {})
|
139
|
+
log.info { "out_slack: post_message #{params}" }
|
140
|
+
post(endpoint, params)
|
141
|
+
end
|
142
|
+
|
143
|
+
private
|
144
|
+
|
145
|
+
def encode_body(params = {})
|
146
|
+
# https://api.slack.com/docs/formatting
|
147
|
+
to_json_with_scrub!(params).gsub(/&/, '&').gsub(/</, '<').gsub(/>/, '>')
|
148
|
+
end
|
149
|
+
|
150
|
+
def response_check(res, params)
|
151
|
+
super
|
152
|
+
unless res.body == 'ok'
|
153
|
+
raise Error.new(res, params)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
# Slack client for Slackbot Remote Control
|
159
|
+
# https://api.slack.com/slackbot
|
160
|
+
class Slackbot < Base
|
161
|
+
def initialize(endpoint, https_proxy = nil)
|
162
|
+
super
|
163
|
+
end
|
164
|
+
|
165
|
+
def api
|
166
|
+
@api ||= WebApi.new(nil, https_proxy)
|
167
|
+
end
|
168
|
+
|
169
|
+
def post_message(params = {}, opts = {})
|
170
|
+
raise ArgumentError, "channel parameter is required" unless params[:channel]
|
171
|
+
with_channels_create(params, opts) do
|
172
|
+
log.info { "out_slack: post_message #{filter_params(params)}" }
|
173
|
+
post(slackbot_endpoint(params), params)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
private
|
178
|
+
|
179
|
+
def slackbot_endpoint(params)
|
180
|
+
endpoint.dup.tap {|e| e.query += "&channel=#{URI.encode(params[:channel])}" }
|
181
|
+
end
|
182
|
+
|
183
|
+
def encode_body(params = {})
|
184
|
+
return params[:text]if params[:text]
|
185
|
+
unless params[:attachments]
|
186
|
+
raise ArgumentError, 'params[:text] or params[:attachments] is required'
|
187
|
+
end
|
188
|
+
# handle params[:attachments]
|
189
|
+
attachment = Array(params[:attachments]).first # see only the first for now
|
190
|
+
# {
|
191
|
+
# attachments: [{
|
192
|
+
# text: "HERE",
|
193
|
+
# }]
|
194
|
+
# }
|
195
|
+
text = attachment[:text]
|
196
|
+
# {
|
197
|
+
# attachments: [{
|
198
|
+
# fields: [{
|
199
|
+
# title: "title",
|
200
|
+
# value: "HERE",
|
201
|
+
# }]
|
202
|
+
# }]
|
203
|
+
# }
|
204
|
+
if text.nil? and attachment[:fields]
|
205
|
+
text = Array(attachment[:fields]).first[:value] # see only the first for now
|
206
|
+
end
|
207
|
+
text
|
208
|
+
end
|
209
|
+
|
210
|
+
def response_check(res, params)
|
211
|
+
if res.body == 'channel_not_found'
|
212
|
+
raise ChannelNotFoundError.new(res, params)
|
213
|
+
elsif res.body != 'ok'
|
214
|
+
raise Error.new(res, params)
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
# Slack client for Web API
|
220
|
+
class WebApi < Base
|
221
|
+
DEFAULT_ENDPOINT = "https://slack.com/api/".freeze
|
222
|
+
|
223
|
+
def api
|
224
|
+
self
|
225
|
+
end
|
226
|
+
|
227
|
+
def endpoint
|
228
|
+
@endpoint ||= URI.parse(DEFAULT_ENDPOINT)
|
229
|
+
end
|
230
|
+
|
231
|
+
def post_message_endpoint
|
232
|
+
@post_message_endpoint ||= URI.join(endpoint, "chat.postMessage")
|
233
|
+
end
|
234
|
+
|
235
|
+
def channels_create_endpoint
|
236
|
+
@channels_create_endpoint ||= URI.join(endpoint, "channels.create")
|
237
|
+
end
|
238
|
+
|
239
|
+
# Sends a message to a channel.
|
240
|
+
#
|
241
|
+
# @see https://api.slack.com/methods/chat.postMessage
|
242
|
+
# @see https://github.com/slackhq/slack-api-docs/blob/master/methods/chat.postMessage.md
|
243
|
+
# @see https://github.com/slackhq/slack-api-docs/blob/master/methods/chat.postMessage.json
|
244
|
+
def post_message(params = {}, opts = {})
|
245
|
+
with_channels_create(params, opts) do
|
246
|
+
log.info { "out_slack: post_message #{filter_params(params)}" }
|
247
|
+
post(post_message_endpoint, params)
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
# Creates a channel.
|
252
|
+
#
|
253
|
+
# NOTE: Bot user can not create a channel. Token must be issued by Normal User Account
|
254
|
+
# @see https://api.slack.com/bot-users
|
255
|
+
#
|
256
|
+
# @see https://api.slack.com/methods/channels.create
|
257
|
+
# @see https://github.com/slackhq/slack-api-docs/blob/master/methods/channels.create.md
|
258
|
+
# @see https://github.com/slackhq/slack-api-docs/blob/master/methods/channels.create.json
|
259
|
+
def channels_create(params = {}, opts = {})
|
260
|
+
log.info { "out_slack: channels_create #{filter_params(params)}" }
|
261
|
+
post(channels_create_endpoint, params)
|
262
|
+
end
|
263
|
+
|
264
|
+
private
|
265
|
+
|
266
|
+
def encode_body(params = {})
|
267
|
+
body = params.dup
|
268
|
+
if params[:attachments]
|
269
|
+
body[:attachments] = to_json_with_scrub!(params[:attachments])
|
270
|
+
end
|
271
|
+
URI.encode_www_form(body)
|
272
|
+
end
|
273
|
+
|
274
|
+
def response_check(res, params)
|
275
|
+
super
|
276
|
+
res_params = JSON.parse(res.body)
|
277
|
+
return if res_params['ok']
|
278
|
+
case res_params['error']
|
279
|
+
when 'channel_not_found'
|
280
|
+
raise ChannelNotFoundError.new(res, params)
|
281
|
+
when 'name_taken'
|
282
|
+
raise NameTakenError.new(res, params)
|
283
|
+
else
|
284
|
+
raise Error.new(res, params)
|
285
|
+
end
|
286
|
+
end
|
287
|
+
end
|
288
|
+
end
|
289
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
|
3
|
+
module Fluent
|
4
|
+
module SlackClient
|
5
|
+
class Error < StandardError
|
6
|
+
attr_reader :res, :req_params
|
7
|
+
|
8
|
+
def initialize(res, req_params = {})
|
9
|
+
@res = res
|
10
|
+
@req_params = req_params.dup
|
11
|
+
end
|
12
|
+
|
13
|
+
def message
|
14
|
+
@req_params[:token] = '[FILTERED]' if @req_params[:token]
|
15
|
+
"res.code:#{@res.code}, res.body:#{@res.body}, req_params:#{@req_params}"
|
16
|
+
end
|
17
|
+
|
18
|
+
alias :to_s :message
|
19
|
+
end
|
20
|
+
|
21
|
+
class ChannelNotFoundError < Error; end
|
22
|
+
class NameTakenError < Error; end
|
23
|
+
end
|
24
|
+
end
|
data/test.sh
ADDED
@@ -0,0 +1,566 @@
|
|
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
|
+
@icon_url = 'http://www.google.com/s2/favicons?domain=www.google.de'
|
11
|
+
end
|
12
|
+
|
13
|
+
CONFIG = %[
|
14
|
+
channel channel
|
15
|
+
webhook_url https://hooks.slack.com/services/XXXX/XXXX/XXX
|
16
|
+
]
|
17
|
+
|
18
|
+
def default_payload
|
19
|
+
{
|
20
|
+
channel: '#channel',
|
21
|
+
mrkdwn: true,
|
22
|
+
link_names: true,
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
def default_attachment
|
27
|
+
{
|
28
|
+
mrkdwn_in: %w[text fields],
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
def create_driver(conf = CONFIG)
|
33
|
+
Fluent::Test::BufferedOutputTestDriver.new(Fluent::SlackOutput).configure(conf)
|
34
|
+
end
|
35
|
+
|
36
|
+
# old version compatibility with v0.4.0"
|
37
|
+
def test_old_config
|
38
|
+
# default check
|
39
|
+
d = create_driver
|
40
|
+
assert_equal true, d.instance.localtime
|
41
|
+
assert_equal nil, d.instance.username # 'fluentd' break lower version compatibility
|
42
|
+
assert_equal nil, d.instance.color # 'good' break lower version compatibility
|
43
|
+
assert_equal nil, d.instance.icon_emoji # ':question:' break lower version compatibility
|
44
|
+
assert_equal nil, d.instance.icon_url
|
45
|
+
assert_equal true, d.instance.mrkdwn
|
46
|
+
assert_equal true, d.instance.link_names
|
47
|
+
assert_equal nil, d.instance.parse
|
48
|
+
|
49
|
+
assert_nothing_raised do
|
50
|
+
create_driver(CONFIG + %[api_key testtoken])
|
51
|
+
end
|
52
|
+
|
53
|
+
# incoming webhook endpoint was changed. team option should be ignored
|
54
|
+
assert_nothing_raised do
|
55
|
+
create_driver(CONFIG + %[team sowasowa])
|
56
|
+
end
|
57
|
+
|
58
|
+
# rtm? it was not calling `rtm.start`. rtm option was removed and should be ignored
|
59
|
+
assert_nothing_raised do
|
60
|
+
create_driver(CONFIG + %[rtm true])
|
61
|
+
end
|
62
|
+
|
63
|
+
# channel should be URI.unescape-ed
|
64
|
+
d = create_driver(CONFIG + %[channel %23test])
|
65
|
+
assert_equal '#test', d.instance.channel
|
66
|
+
|
67
|
+
# timezone should work
|
68
|
+
d = create_driver(CONFIG + %[timezone Asia/Tokyo])
|
69
|
+
assert_equal 'Asia/Tokyo', d.instance.timezone
|
70
|
+
end
|
71
|
+
|
72
|
+
def test_configure
|
73
|
+
d = create_driver(%[
|
74
|
+
channel channel
|
75
|
+
time_format %Y/%m/%d %H:%M:%S
|
76
|
+
username username
|
77
|
+
color bad
|
78
|
+
icon_emoji :ghost:
|
79
|
+
token XX-XX-XX
|
80
|
+
title slack notice!
|
81
|
+
message %s
|
82
|
+
message_keys message
|
83
|
+
])
|
84
|
+
assert_equal '#channel', d.instance.channel
|
85
|
+
assert_equal '%Y/%m/%d %H:%M:%S', d.instance.time_format
|
86
|
+
assert_equal 'username', d.instance.username
|
87
|
+
assert_equal 'bad', d.instance.color
|
88
|
+
assert_equal ':ghost:', d.instance.icon_emoji
|
89
|
+
assert_equal 'XX-XX-XX', d.instance.token
|
90
|
+
assert_equal '%s', d.instance.message
|
91
|
+
assert_equal ['message'], d.instance.message_keys
|
92
|
+
|
93
|
+
# Allow DM
|
94
|
+
d = create_driver(CONFIG + %[channel @test])
|
95
|
+
assert_equal '@test', d.instance.channel
|
96
|
+
|
97
|
+
assert_raise(Fluent::ConfigError) do
|
98
|
+
create_driver(CONFIG + %[title %s %s\ntitle_keys foo])
|
99
|
+
end
|
100
|
+
|
101
|
+
assert_raise(Fluent::ConfigError) do
|
102
|
+
create_driver(CONFIG + %[message %s %s\nmessage_keys foo])
|
103
|
+
end
|
104
|
+
|
105
|
+
assert_raise(Fluent::ConfigError) do
|
106
|
+
create_driver(CONFIG + %[channel %s %s\nchannel_keys foo])
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
def test_slack_configure
|
111
|
+
# One of webhook_url or slackbot_url, or token is required
|
112
|
+
assert_raise(Fluent::ConfigError) do
|
113
|
+
create_driver(%[channel foo])
|
114
|
+
end
|
115
|
+
|
116
|
+
# webhook_url is an empty string
|
117
|
+
assert_raise(Fluent::ConfigError) do
|
118
|
+
create_driver(%[channel foo\nwebhook_url])
|
119
|
+
end
|
120
|
+
|
121
|
+
# webhook without channel (it works because webhook has a default channel)
|
122
|
+
assert_nothing_raised do
|
123
|
+
create_driver(%[webhook_url https://example.com/path/to/webhook])
|
124
|
+
end
|
125
|
+
|
126
|
+
# slackbot_url is an empty string
|
127
|
+
assert_raise(Fluent::ConfigError) do
|
128
|
+
create_driver(%[channel foo\nslackbot_url])
|
129
|
+
end
|
130
|
+
|
131
|
+
# slackbot without channel
|
132
|
+
assert_raise(Fluent::ConfigError) do
|
133
|
+
create_driver(%[slackbot_url https://example.com/path/to/slackbot])
|
134
|
+
end
|
135
|
+
|
136
|
+
# token is an empty string
|
137
|
+
assert_raise(Fluent::ConfigError) do
|
138
|
+
create_driver(%[channel foo\ntoken])
|
139
|
+
end
|
140
|
+
|
141
|
+
# slack webapi token without channel
|
142
|
+
assert_raise(Fluent::ConfigError) do
|
143
|
+
create_driver(%[token some_token])
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def test_timezone_configure
|
148
|
+
time = Time.parse("2014-01-01 22:00:00 UTC").to_i
|
149
|
+
|
150
|
+
d = create_driver(CONFIG + %[localtime])
|
151
|
+
with_timezone('Asia/Tokyo') do
|
152
|
+
assert_equal true, d.instance.localtime
|
153
|
+
assert_equal "07:00:00", d.instance.timef.format(time)
|
154
|
+
end
|
155
|
+
|
156
|
+
d = create_driver(CONFIG + %[utc])
|
157
|
+
with_timezone('Asia/Tokyo') do
|
158
|
+
assert_equal false, d.instance.localtime
|
159
|
+
assert_equal "22:00:00", d.instance.timef.format(time)
|
160
|
+
end
|
161
|
+
|
162
|
+
d = create_driver(CONFIG + %[timezone Asia/Taipei])
|
163
|
+
with_timezone('Asia/Tokyo') do
|
164
|
+
assert_equal "Asia/Taipei", d.instance.timezone
|
165
|
+
assert_equal "06:00:00", d.instance.timef.format(time)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
def test_time_format_configure
|
170
|
+
time = Time.parse("2014-01-01 22:00:00 UTC").to_i
|
171
|
+
|
172
|
+
d = create_driver(CONFIG + %[time_format %Y/%m/%d %H:%M:%S])
|
173
|
+
with_timezone('Asia/Tokyo') do
|
174
|
+
assert_equal "2014/01/02 07:00:00", d.instance.timef.format(time)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def test_buffer_configure
|
179
|
+
assert_nothing_raised do
|
180
|
+
create_driver(CONFIG + %[buffer_type file\nbuffer_path tmp/])
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
def test_icon_configure
|
185
|
+
# default
|
186
|
+
d = create_driver(CONFIG)
|
187
|
+
assert_equal nil, d.instance.icon_emoji
|
188
|
+
assert_equal nil, d.instance.icon_url
|
189
|
+
|
190
|
+
# either of icon_emoji or icon_url can be specified
|
191
|
+
assert_raise(Fluent::ConfigError) do
|
192
|
+
d = create_driver(CONFIG + %[icon_emoji :ghost:\nicon_url #{@icon_url}])
|
193
|
+
end
|
194
|
+
|
195
|
+
# icon_emoji
|
196
|
+
d = create_driver(CONFIG + %[icon_emoji :ghost:])
|
197
|
+
assert_equal ':ghost:', d.instance.icon_emoji
|
198
|
+
assert_equal nil, d.instance.icon_url
|
199
|
+
|
200
|
+
# icon_url
|
201
|
+
d = create_driver(CONFIG + %[icon_url #{@icon_url}])
|
202
|
+
assert_equal nil, d.instance.icon_emoji
|
203
|
+
assert_equal @icon_url, d.instance.icon_url
|
204
|
+
end
|
205
|
+
|
206
|
+
def test_link_names_configure
|
207
|
+
# default
|
208
|
+
d = create_driver(CONFIG)
|
209
|
+
assert_equal true, d.instance.link_names
|
210
|
+
|
211
|
+
# true
|
212
|
+
d = create_driver(CONFIG + %[link_names true])
|
213
|
+
assert_equal true, d.instance.link_names
|
214
|
+
|
215
|
+
# false
|
216
|
+
d = create_driver(CONFIG + %[link_names false])
|
217
|
+
assert_equal false, d.instance.link_names
|
218
|
+
end
|
219
|
+
|
220
|
+
def test_parse_configure
|
221
|
+
# default
|
222
|
+
d = create_driver(CONFIG)
|
223
|
+
assert_equal nil, d.instance.parse
|
224
|
+
|
225
|
+
# none
|
226
|
+
d = create_driver(CONFIG + %[parse none])
|
227
|
+
assert_equal 'none', d.instance.parse
|
228
|
+
|
229
|
+
# full
|
230
|
+
d = create_driver(CONFIG + %[parse full])
|
231
|
+
assert_equal 'full', d.instance.parse
|
232
|
+
|
233
|
+
# invalid
|
234
|
+
assert_raise(Fluent::ConfigError) do
|
235
|
+
d = create_driver(CONFIG + %[parse invalid])
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
def test_mrkwn_configure
|
240
|
+
# default
|
241
|
+
d = create_driver(CONFIG)
|
242
|
+
assert_equal true, d.instance.mrkdwn
|
243
|
+
assert_equal %w[text fields], d.instance.mrkdwn_in
|
244
|
+
|
245
|
+
# true
|
246
|
+
d = create_driver(CONFIG + %[mrkdwn true])
|
247
|
+
assert_equal true, d.instance.mrkdwn
|
248
|
+
assert_equal %w[text fields], d.instance.mrkdwn_in
|
249
|
+
|
250
|
+
# false
|
251
|
+
d = create_driver(CONFIG + %[mrkdwn false])
|
252
|
+
assert_equal false, d.instance.mrkdwn
|
253
|
+
assert_equal nil, d.instance.mrkdwn_in
|
254
|
+
end
|
255
|
+
|
256
|
+
def test_https_proxy_configure
|
257
|
+
# default
|
258
|
+
d = create_driver(CONFIG)
|
259
|
+
assert_equal nil, d.instance.slack.https_proxy
|
260
|
+
assert_equal Net::HTTP, d.instance.slack.proxy_class
|
261
|
+
|
262
|
+
# https_proxy
|
263
|
+
d = create_driver(CONFIG + %[https_proxy https://proxy.foo.bar:443])
|
264
|
+
assert_equal URI.parse('https://proxy.foo.bar:443'), d.instance.slack.https_proxy
|
265
|
+
assert_not_equal Net::HTTP, d.instance.slack.proxy_class # Net::HTTP.Proxy
|
266
|
+
end
|
267
|
+
|
268
|
+
def test_auto_channels_create_configure
|
269
|
+
# default
|
270
|
+
d = create_driver(CONFIG)
|
271
|
+
assert_equal false, d.instance.auto_channels_create
|
272
|
+
assert_equal({}, d.instance.post_message_opts)
|
273
|
+
|
274
|
+
# require `token`
|
275
|
+
assert_raise(Fluent::ConfigError) do
|
276
|
+
d = create_driver(CONFIG + %[auto_channels_create true])
|
277
|
+
end
|
278
|
+
|
279
|
+
# auto_channels_create
|
280
|
+
d = create_driver(CONFIG + %[auto_channels_create true\ntoken XXX-XX-XXX])
|
281
|
+
assert_equal true, d.instance.auto_channels_create
|
282
|
+
assert_equal({auto_channels_create: true}, d.instance.post_message_opts)
|
283
|
+
end
|
284
|
+
|
285
|
+
def test_default_incoming_webhook
|
286
|
+
d = create_driver(%[
|
287
|
+
channel channel
|
288
|
+
webhook_url https://hooks.slack.com/services/XXX/XXX/XXX
|
289
|
+
])
|
290
|
+
assert_equal Fluent::SlackClient::IncomingWebhook, d.instance.slack.class
|
291
|
+
time = Time.parse("2014-01-01 22:00:00 UTC").to_i
|
292
|
+
d.tag = 'test'
|
293
|
+
mock(d.instance.slack).post_message(default_payload.merge({
|
294
|
+
text: "sowawa1\nsowawa2\n",
|
295
|
+
}), {})
|
296
|
+
with_timezone('Asia/Tokyo') do
|
297
|
+
d.emit({message: 'sowawa1'}, time)
|
298
|
+
d.emit({message: 'sowawa2'}, time)
|
299
|
+
d.run
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
def test_default_slackbot
|
304
|
+
d = create_driver(%[
|
305
|
+
channel channel
|
306
|
+
slackbot_url https://xxxxx.slack.com/services/hooks/slackbot?token=XXXXXXX
|
307
|
+
])
|
308
|
+
assert_equal Fluent::SlackClient::Slackbot, d.instance.slack.class
|
309
|
+
time = Time.parse("2014-01-01 22:00:00 UTC").to_i
|
310
|
+
d.tag = 'test'
|
311
|
+
mock(d.instance.slack).post_message(default_payload.merge({
|
312
|
+
text: "sowawa1\nsowawa2\n",
|
313
|
+
}), {})
|
314
|
+
with_timezone('Asia/Tokyo') do
|
315
|
+
d.emit({message: 'sowawa1'}, time)
|
316
|
+
d.emit({message: 'sowawa2'}, time)
|
317
|
+
d.run
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
def test_default_slack_api
|
322
|
+
d = create_driver(%[
|
323
|
+
channel channel
|
324
|
+
token XX-XX-XX
|
325
|
+
])
|
326
|
+
assert_equal Fluent::SlackClient::WebApi, d.instance.slack.class
|
327
|
+
time = Time.parse("2014-01-01 22:00:00 UTC").to_i
|
328
|
+
d.tag = 'test'
|
329
|
+
mock(d.instance.slack).post_message(default_payload.merge({
|
330
|
+
token: 'XX-XX-XX',
|
331
|
+
text: "sowawa1\nsowawa2\n",
|
332
|
+
}), {})
|
333
|
+
with_timezone('Asia/Tokyo') do
|
334
|
+
d.emit({message: 'sowawa1'}, time)
|
335
|
+
d.emit({message: 'sowawa2'}, time)
|
336
|
+
d.run
|
337
|
+
end
|
338
|
+
end
|
339
|
+
|
340
|
+
def test_title_payload
|
341
|
+
title = "mytitle"
|
342
|
+
d = create_driver(CONFIG + %[title #{title}])
|
343
|
+
time = Time.parse("2014-01-01 22:00:00 UTC").to_i
|
344
|
+
d.tag = 'test'
|
345
|
+
# attachments field should be changed to show the title
|
346
|
+
mock(d.instance.slack).post_message(default_payload.merge({
|
347
|
+
attachments: [default_attachment.merge({
|
348
|
+
fallback: title,
|
349
|
+
fields: [
|
350
|
+
{
|
351
|
+
title: title,
|
352
|
+
value: "sowawa1\nsowawa2\n",
|
353
|
+
}
|
354
|
+
],
|
355
|
+
})]
|
356
|
+
}), {})
|
357
|
+
with_timezone('Asia/Tokyo') do
|
358
|
+
d.emit({message: 'sowawa1'}, time)
|
359
|
+
d.emit({message: 'sowawa2'}, time)
|
360
|
+
d.run
|
361
|
+
end
|
362
|
+
end
|
363
|
+
|
364
|
+
def test_title_payload_with_verbose_fallback_option
|
365
|
+
title = "mytitle"
|
366
|
+
d = create_driver(CONFIG + %[title #{title}\nverbose_fallback true])
|
367
|
+
time = Time.parse("2014-01-01 22:00:00 UTC").to_i
|
368
|
+
d.tag = 'test'
|
369
|
+
# attachments field should be changed to show the title
|
370
|
+
mock(d.instance.slack).post_message(default_payload.merge({
|
371
|
+
attachments: [default_attachment.merge({
|
372
|
+
fallback: "#{title} sowawa1\nsowawa2\n",
|
373
|
+
fields: [
|
374
|
+
{
|
375
|
+
title: title,
|
376
|
+
value: "sowawa1\nsowawa2\n",
|
377
|
+
}
|
378
|
+
],
|
379
|
+
})]
|
380
|
+
}), {})
|
381
|
+
with_timezone('Asia/Tokyo') do
|
382
|
+
d.emit({message: 'sowawa1'}, time)
|
383
|
+
d.emit({message: 'sowawa2'}, time)
|
384
|
+
d.run
|
385
|
+
end
|
386
|
+
end
|
387
|
+
|
388
|
+
def test_color_payload
|
389
|
+
color = 'good'
|
390
|
+
d = create_driver(CONFIG + %[color #{color}])
|
391
|
+
time = Time.parse("2014-01-01 22:00:00 UTC").to_i
|
392
|
+
d.tag = 'test'
|
393
|
+
# attachments field should be changed to show the title
|
394
|
+
mock(d.instance.slack).post_message(default_payload.merge({
|
395
|
+
attachments: [default_attachment.merge({
|
396
|
+
color: color,
|
397
|
+
fallback: "sowawa1\nsowawa2\n",
|
398
|
+
text: "sowawa1\nsowawa2\n",
|
399
|
+
})]
|
400
|
+
}), {})
|
401
|
+
with_timezone('Asia/Tokyo') do
|
402
|
+
d.emit({message: 'sowawa1'}, time)
|
403
|
+
d.emit({message: 'sowawa2'}, time)
|
404
|
+
d.run
|
405
|
+
end
|
406
|
+
end
|
407
|
+
|
408
|
+
def test_plain_payload
|
409
|
+
d = create_driver(CONFIG)
|
410
|
+
time = Time.parse("2014-01-01 22:00:00 UTC").to_i
|
411
|
+
d.tag = 'test'
|
412
|
+
# attachments field should be changed to show the title
|
413
|
+
mock(d.instance.slack).post_message(default_payload.merge({
|
414
|
+
text: "sowawa1\nsowawa2\n",
|
415
|
+
}), {})
|
416
|
+
with_timezone('Asia/Tokyo') do
|
417
|
+
d.emit({message: 'sowawa1'}, time)
|
418
|
+
d.emit({message: 'sowawa2'}, time)
|
419
|
+
d.run
|
420
|
+
end
|
421
|
+
end
|
422
|
+
|
423
|
+
def test_title_keys
|
424
|
+
d = create_driver(CONFIG + %[title [%s] %s\ntitle_keys time,tag])
|
425
|
+
time = Time.parse("2014-01-01 22:00:00 UTC").to_i
|
426
|
+
d.tag = 'test'
|
427
|
+
# attachments field should be changed to show the title
|
428
|
+
mock(d.instance.slack).post_message(default_payload.merge({
|
429
|
+
attachments: [default_attachment.merge({
|
430
|
+
fallback: "[07:00:00] #{d.tag}",
|
431
|
+
fields: [
|
432
|
+
{
|
433
|
+
title: "[07:00:00] #{d.tag}",
|
434
|
+
value: "sowawa1\nsowawa2\n",
|
435
|
+
}
|
436
|
+
],
|
437
|
+
})]
|
438
|
+
}), {})
|
439
|
+
with_timezone('Asia/Tokyo') do
|
440
|
+
d.emit({message: 'sowawa1'}, time)
|
441
|
+
d.emit({message: 'sowawa2'}, time)
|
442
|
+
d.run
|
443
|
+
end
|
444
|
+
end
|
445
|
+
|
446
|
+
def test_message_keys
|
447
|
+
d = create_driver(CONFIG + %[message [%s] %s %s\nmessage_keys time,tag,message])
|
448
|
+
time = Time.parse("2014-01-01 22:00:00 UTC").to_i
|
449
|
+
d.tag = 'test'
|
450
|
+
mock(d.instance.slack).post_message(default_payload.merge({
|
451
|
+
text: "[07:00:00] test sowawa1\n[07:00:00] test sowawa2\n",
|
452
|
+
}), {})
|
453
|
+
with_timezone('Asia/Tokyo') do
|
454
|
+
d.emit({message: 'sowawa1'}, time)
|
455
|
+
d.emit({message: 'sowawa2'}, time)
|
456
|
+
d.run
|
457
|
+
end
|
458
|
+
end
|
459
|
+
|
460
|
+
def test_channel_keys
|
461
|
+
d = create_driver(CONFIG + %[channel %s\nchannel_keys channel])
|
462
|
+
time = Time.parse("2014-01-01 22:00:00 UTC").to_i
|
463
|
+
d.tag = 'test'
|
464
|
+
mock(d.instance.slack).post_message(default_payload.merge({
|
465
|
+
channel: '#channel1',
|
466
|
+
text: "sowawa1\n",
|
467
|
+
}), {})
|
468
|
+
mock(d.instance.slack).post_message(default_payload.merge({
|
469
|
+
channel: '#channel2',
|
470
|
+
text: "sowawa2\n",
|
471
|
+
}), {})
|
472
|
+
with_timezone('Asia/Tokyo') do
|
473
|
+
d.emit({message: 'sowawa1', channel: 'channel1'}, time)
|
474
|
+
d.emit({message: 'sowawa2', channel: 'channel2'}, time)
|
475
|
+
d.run
|
476
|
+
end
|
477
|
+
end
|
478
|
+
|
479
|
+
def test_icon_emoji
|
480
|
+
d = create_driver(CONFIG + %[icon_emoji :ghost:])
|
481
|
+
time = Time.parse("2014-01-01 22:00:00 UTC").to_i
|
482
|
+
d.tag = 'test'
|
483
|
+
mock(d.instance.slack).post_message(default_payload.merge({
|
484
|
+
icon_emoji: ':ghost:',
|
485
|
+
text: "foo\n",
|
486
|
+
}), {})
|
487
|
+
with_timezone('Asia/Tokyo') do
|
488
|
+
d.emit({message: 'foo'}, time)
|
489
|
+
d.run
|
490
|
+
end
|
491
|
+
end
|
492
|
+
|
493
|
+
def test_icon_url
|
494
|
+
d = create_driver(CONFIG + %[icon_url #{@icon_url}])
|
495
|
+
time = Time.parse("2014-01-01 22:00:00 UTC").to_i
|
496
|
+
d.tag = 'test'
|
497
|
+
mock(d.instance.slack).post_message(default_payload.merge({
|
498
|
+
icon_url: @icon_url,
|
499
|
+
text: "foo\n",
|
500
|
+
}), {})
|
501
|
+
with_timezone('Asia/Tokyo') do
|
502
|
+
d.emit({message: 'foo'}, time)
|
503
|
+
d.run
|
504
|
+
end
|
505
|
+
end
|
506
|
+
|
507
|
+
def test_mrkdwn
|
508
|
+
d = create_driver(CONFIG + %[mrkdwn true])
|
509
|
+
time = Time.parse("2014-01-01 22:00:00 UTC").to_i
|
510
|
+
d.tag = 'test'
|
511
|
+
mock(d.instance.slack).post_message(default_payload.merge({
|
512
|
+
mrkdwn: true,
|
513
|
+
text: "foo\n",
|
514
|
+
}), {})
|
515
|
+
with_timezone('Asia/Tokyo') do
|
516
|
+
d.emit({message: 'foo'}, time)
|
517
|
+
d.run
|
518
|
+
end
|
519
|
+
end
|
520
|
+
|
521
|
+
def test_mrkdwn_in
|
522
|
+
d = create_driver(CONFIG + %[mrkdwn true\ncolor good])
|
523
|
+
time = Time.parse("2014-01-01 22:00:00 UTC").to_i
|
524
|
+
d.tag = 'test'
|
525
|
+
mock(d.instance.slack).post_message(default_payload.merge({
|
526
|
+
attachments: [default_attachment.merge({
|
527
|
+
color: "good",
|
528
|
+
fallback: "foo\n",
|
529
|
+
text: "foo\n",
|
530
|
+
mrkdwn_in: ["text", "fields"],
|
531
|
+
})]
|
532
|
+
}), {})
|
533
|
+
with_timezone('Asia/Tokyo') do
|
534
|
+
d.emit({message: 'foo'}, time)
|
535
|
+
d.run
|
536
|
+
end
|
537
|
+
end
|
538
|
+
|
539
|
+
def test_link_names
|
540
|
+
d = create_driver(CONFIG + %[link_names true])
|
541
|
+
time = Time.parse("2014-01-01 22:00:00 UTC").to_i
|
542
|
+
d.tag = 'test'
|
543
|
+
mock(d.instance.slack).post_message(default_payload.merge({
|
544
|
+
link_names: true,
|
545
|
+
text: "foo\n",
|
546
|
+
}), {})
|
547
|
+
with_timezone('Asia/Tokyo') do
|
548
|
+
d.emit({message: 'foo'}, time)
|
549
|
+
d.run
|
550
|
+
end
|
551
|
+
end
|
552
|
+
|
553
|
+
def test_parse
|
554
|
+
d = create_driver(CONFIG + %[parse full])
|
555
|
+
time = Time.parse("2014-01-01 22:00:00 UTC").to_i
|
556
|
+
d.tag = 'test'
|
557
|
+
mock(d.instance.slack).post_message(default_payload.merge({
|
558
|
+
parse: "full",
|
559
|
+
text: "foo\n",
|
560
|
+
}), {})
|
561
|
+
with_timezone('Asia/Tokyo') do
|
562
|
+
d.emit({message: 'foo'}, time)
|
563
|
+
d.run
|
564
|
+
end
|
565
|
+
end
|
566
|
+
end
|