fluent-plugin-slack 0.5.4 → 0.5.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ad20f86b455e8ca489fe6ed2be1aea310505174d
4
- data.tar.gz: 6a8410c533109fd41cc92c8ee3008841536264f9
3
+ metadata.gz: f75a082b3fc4e977a269cd78744064139e102945
4
+ data.tar.gz: c9a28cc8cfafa9b4bb80522b2b6261de9b4af55a
5
5
  SHA512:
6
- metadata.gz: 132d099d24bfe70cb2aa297f2a63ec987981b120e27328d51137896ec2f3b685d7074cefefdcd3ac042fa44ca72ff9a01ba90b6899d0d3f81c3b49a30023d243
7
- data.tar.gz: d8a789673b0f0c8e68c39abe231a313366c8f1f8e2cd8d24eb26c8022160b72041c83c0b584ba9f2b361f52a56444bd0e43e7cad8ade9fdd760671756607b84c
6
+ metadata.gz: fc23d99226fe190408dbbb144325a90de7d19af07d67ab54544170059124cb1c6e03b68610aa4e976abeeca47cafc12b4d5120e8c67ff8f7301157d199911114
7
+ data.tar.gz: 1eca5ba6bac83f01942b4aec43a24327b5c50d04cdf964c0478dec2d0ce2afbcecea49d492bfc0061a1478186f33aabbad3512afdd43984020fb82e80c818561
data/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ ## 0.5.5 (2015/04/01)
2
+
3
+ Enhancements:
4
+
5
+ * Support Slackbot Remote Control API
6
+
1
7
  ## 0.5.4 (2015/03/31)
2
8
 
3
9
  Enhancements:
data/README.md CHANGED
@@ -26,6 +26,23 @@ fluent_logger.post('slack', {
26
26
  })
27
27
  ```
28
28
 
29
+ # Usage (Slackbot)
30
+
31
+ ```apache
32
+ <match slack>
33
+ type slack
34
+ slackbot_url https://xxxx.slack.com/services/hooks/slackbot?token=XXXXXXXXX
35
+ channel general
36
+ flush_interval 60s
37
+ </match>
38
+ ```
39
+
40
+ ```ruby
41
+ fluent_logger.post('slack', {
42
+ :message => 'Hello<br>World!'
43
+ })
44
+ ```
45
+
29
46
  # Usage (Web API)
30
47
 
31
48
  ```apache
@@ -50,8 +67,9 @@ fluent_logger.post('slack', {
50
67
 
51
68
  |parameter|description|default|
52
69
  |---|---|---|
53
- |webhook_url|Incoming Webhook URI (Required for Incoming Webhook mode)||
54
- |token|Token for Web API (Required for Web API mode)||
70
+ |webhook_url|Incoming Webhook URI (Required for Incoming Webhook mode). See https://api.slack.com/incoming-webhooks||
71
+ |slackbot_url|Slackbot URI (Required for Slackbot mode). See https://api.slack.com/slackbot. `username`, `color`, `icon_emoji`, `icon_url` are not available for this mode, but Desktop Notification via Highlight Words works with only this mode (Notification via Mentions works from Incoming Webhook and Web API with link_names=1, but Notification via Highlight Words does not)||
72
+ |token|Token for Web API (Required for Web API mode). See https://api.slack.com/web||
55
73
  |username|name of bot|fluentd|
56
74
  |color|color to use|good|
57
75
  |icon_emoji|emoji to use as the icon. either of icon_emoji or icon_url can be specified|`:question:`|
@@ -63,7 +81,7 @@ fluent_logger.post('slack', {
63
81
  |title_keys|keys used to format the title|nil|
64
82
  |message|message format. %s will be replaced with value specified by message_keys|%s|
65
83
  |message_keys|keys used to format messages|message|
66
- |auto_channels_create|Create channels if not exist. Available only with Web API mode, and a token for Normal User is required (Bot User can not create channels. See https://api.slack.com/bot-users)|false|
84
+ |auto_channels_create|Create channels if not exist. Not available for Incoming Webhook mode (since Incoming Webhook is specific to a channel). A web api `token` for Normal User is required (Bot User can not create channels. See https://api.slack.com/bot-users)|false|
67
85
  |https_proxy|https proxy url such as `https://proxy.foo.bar:443`|nil|
68
86
 
69
87
  `fluent-plugin-slack` uses `SetTimeKeyMixin` and `SetTagKeyMixin`, so you can also use:
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.5.4
1
+ 0.5.5
@@ -12,6 +12,7 @@ module Fluent
12
12
  config_set_default :include_tag_key, true
13
13
 
14
14
  config_param :webhook_url, :string, default: nil # incoming webhook
15
+ config_param :slackbot_url, :string, default: nil # slackbot
15
16
  config_param :token, :string, default: nil # api token
16
17
  config_param :username, :string, default: 'fluentd'
17
18
  config_param :color, :string, default: 'good'
@@ -61,6 +62,13 @@ module Fluent
61
62
  @message ||= '[%s] %s'
62
63
  @message_keys ||= %w[time message]
63
64
  @slack = Fluent::SlackClient::IncomingWebhook.new(@webhook_url)
65
+ elsif @slackbot_url
66
+ if @slackbot_url.empty?
67
+ raise Fluent::ConfigError.new("`slackbot_url` is an empty string")
68
+ end
69
+ @message ||= '%s'
70
+ @message_keys ||= %w[message]
71
+ @slack = Fluent::SlackClient::Slackbot.new(@slackbot_url)
64
72
  elsif @token
65
73
  if @token.empty?
66
74
  raise Fluent::ConfigError.new("`token` is an empty string")
@@ -69,7 +77,7 @@ module Fluent
69
77
  @message_keys ||= %w[message]
70
78
  @slack = Fluent::SlackClient::WebApi.new
71
79
  else
72
- raise Fluent::ConfigError.new("Either of `webhook_url` or `token` is required")
80
+ raise Fluent::ConfigError.new("One of `webhook_url` or `slackbot_url`, or `token` is required")
73
81
  end
74
82
  @slack.log = log
75
83
  @slack.debug_dev = log.out if log.level <= Fluent::Log::LEVEL_TRACE
@@ -12,6 +12,7 @@ module Fluent
12
12
  config_set_default :include_tag_key, true
13
13
 
14
14
  config_param :webhook_url, :string, default: nil # incoming webhook
15
+ config_param :slackbot_url, :string, default: nil # slackbot
15
16
  config_param :token, :string, default: nil # api token
16
17
  config_param :username, :string, default: 'fluentd'
17
18
  config_param :color, :string, default: 'good'
@@ -61,6 +62,13 @@ module Fluent
61
62
  @message ||= '[%s] %s'
62
63
  @message_keys ||= %w[time message]
63
64
  @slack = Fluent::SlackClient::IncomingWebhook.new(@webhook_url)
65
+ elsif @slackbot_url
66
+ if @slackbot_url.empty?
67
+ raise Fluent::ConfigError.new("`slackbot_url` is an empty string")
68
+ end
69
+ @message ||= '%s'
70
+ @message_keys ||= %w[message]
71
+ @slack = Fluent::SlackClient::Slackbot.new(@slackbot_url)
64
72
  elsif @token
65
73
  if @token.empty?
66
74
  raise Fluent::ConfigError.new("`token` is an empty string")
@@ -69,7 +77,7 @@ module Fluent
69
77
  @message_keys ||= %w[message]
70
78
  @slack = Fluent::SlackClient::WebApi.new
71
79
  else
72
- raise Fluent::ConfigError.new("Either of `webhook_url` or `token` is required")
80
+ raise Fluent::ConfigError.new("One of `webhook_url` or `slackbot_url`, or `token` is required")
73
81
  end
74
82
  @slack.log = log
75
83
  @slack.debug_dev = log.out if log.level <= Fluent::Log::LEVEL_TRACE
@@ -16,6 +16,9 @@ module Fluent
16
16
  # (Incoming Webhook) required
17
17
  # https://hooks.slack.com/services/XXX/XXX/XXX
18
18
  #
19
+ # (Slackbot) required
20
+ # https://xxxx.slack.com/services/hooks/slackbot?token=XXXXX
21
+ #
19
22
  # (Web API) optional and default to be
20
23
  # https://slack.com/api/
21
24
  #
@@ -48,7 +51,7 @@ module Fluent
48
51
  http.verify_mode = OpenSSL::SSL::VERIFY_NONE
49
52
  http.set_debug_output(debug_dev) if debug_dev
50
53
 
51
- req = Net::HTTP::Post.new(endpoint.path)
54
+ req = Net::HTTP::Post.new(endpoint)
52
55
  req['Host'] = endpoint.host
53
56
  req['Accept'] = 'application/json; charset=utf-8'
54
57
  req['User-Agent'] = 'fluent-plugin-slack'
@@ -69,13 +72,38 @@ module Fluent
69
72
  raise Error.new(res, params)
70
73
  end
71
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
72
100
  end
73
101
 
74
102
  # Slack client for Incoming Webhook
103
+ # https://api.slack.com/incoming-webhooks
75
104
  class IncomingWebhook < Base
76
- def endpoint
77
- return @endpoint if @endpoint
78
- raise ArgumentError, "Incoming Webhook endpoint is not configured"
105
+ def initialize(endpoint, https_proxy = nil)
106
+ super
79
107
  end
80
108
 
81
109
  def post_message(params = {}, opts = {})
@@ -97,10 +125,71 @@ module Fluent
97
125
  end
98
126
  end
99
127
 
128
+ # Slack client for Slackbot Remote Control
129
+ # https://api.slack.com/slackbot
130
+ class Slackbot < Base
131
+ def initialize(endpoint, https_proxy = nil)
132
+ super
133
+ end
134
+
135
+ def api
136
+ @api ||= WebApi.new(nil, https_proxy)
137
+ end
138
+
139
+ def post_message(params = {}, opts = {})
140
+ raise ArgumentError, "channel parameter is required" unless params[:channel]
141
+ with_channels_create(params, opts) do
142
+ log.info { "out_slack: post_message #{filter_params(params)}" }
143
+ post(slackbot_endpoint(params), params)
144
+ end
145
+ end
146
+
147
+ private
148
+
149
+ def slackbot_endpoint(params)
150
+ endpoint.dup.tap {|e| e.query += "&channel=#{URI.encode(params[:channel])}" }
151
+ end
152
+
153
+ def encode_body(params = {})
154
+ raise ArgumentError, 'params[:attachments] is required' unless params[:attachments]
155
+ attachment = Array(params[:attachments]).first # let me see only the first for now
156
+ # {
157
+ # attachments: [{
158
+ # text: "HERE",
159
+ # }]
160
+ # }
161
+ text = attachment[:text]
162
+ # {
163
+ # attachments: [{
164
+ # fields: [{
165
+ # title: "title",
166
+ # value: "HERE",
167
+ # }]
168
+ # }]
169
+ # }
170
+ if text.nil? and attachment[:fields]
171
+ text = Array(attachment[:fields]).first[:value] # let me see only the first for now
172
+ end
173
+ text
174
+ end
175
+
176
+ def response_check(res, params)
177
+ if res.body == 'channel_not_found'
178
+ raise ChannelNotFoundError.new(res, params)
179
+ elsif res.body != 'ok'
180
+ raise Error.new(res, params)
181
+ end
182
+ end
183
+ end
184
+
100
185
  # Slack client for Web API
101
186
  class WebApi < Base
102
187
  DEFAULT_ENDPOINT = "https://slack.com/api/".freeze
103
188
 
189
+ def api
190
+ self
191
+ end
192
+
104
193
  def endpoint
105
194
  @endpoint ||= URI.parse(DEFAULT_ENDPOINT)
106
195
  end
@@ -119,18 +208,9 @@ module Fluent
119
208
  # @see https://github.com/slackhq/slack-api-docs/blob/master/methods/chat.postMessage.md
120
209
  # @see https://github.com/slackhq/slack-api-docs/blob/master/methods/chat.postMessage.json
121
210
  def post_message(params = {}, opts = {})
122
- retries = 1
123
- begin
124
- log.info { "out_slack: post_message #{params.dup.tap {|p| p[:token] = '[FILTERED]' if p[:token] }}" }
211
+ with_channels_create(params, opts) do
212
+ log.info { "out_slack: post_message #{filter_params(params)}" }
125
213
  post(post_message_endpoint, params)
126
- rescue ChannelNotFoundError => e
127
- if opts[:auto_channels_create]
128
- log.warn "out_slack: channel \"#{params[:channel]}\" is not found. try to create the channel, and then retry to post the message."
129
- channels_create({name: params[:channel], token: params[:token]})
130
- retry if (retries -= 1) >= 0 # one time retry
131
- else
132
- raise e
133
- end
134
214
  end
135
215
  end
136
216
 
@@ -143,7 +223,7 @@ module Fluent
143
223
  # @see https://github.com/slackhq/slack-api-docs/blob/master/methods/channels.create.md
144
224
  # @see https://github.com/slackhq/slack-api-docs/blob/master/methods/channels.create.json
145
225
  def channels_create(params = {}, opts = {})
146
- log.info { "out_slack: channels_create #{params.dup.tap {|p| p[:token] = '[FILTERED]' if p[:token] }}" }
226
+ log.info { "out_slack: channels_create #{filter_params(params)}" }
147
227
  post(channels_create_endpoint, params)
148
228
  end
149
229
 
@@ -85,7 +85,7 @@ class SlackOutputTest < Test::Unit::TestCase
85
85
  create_driver(CONFIG + %[channel %s %s\nchannel_keys foo])
86
86
  end
87
87
 
88
- # Either of webhook_url or token is required
88
+ # One of webhook_url or slackbot_url, or token is required
89
89
  assert_raise(Fluent::ConfigError) do
90
90
  create_driver(%[channel foo])
91
91
  end
@@ -95,6 +95,11 @@ class SlackOutputTest < Test::Unit::TestCase
95
95
  create_driver(%[channel foo\nwebhook_url])
96
96
  end
97
97
 
98
+ # webhook_url is an empty string
99
+ assert_raise(Fluent::ConfigError) do
100
+ create_driver(%[channel foo\nslackbot_url])
101
+ end
102
+
98
103
  # token is an empty string
99
104
  assert_raise(Fluent::ConfigError) do
100
105
  create_driver(%[channel foo\ntoken])
@@ -189,6 +194,7 @@ class SlackOutputTest < Test::Unit::TestCase
189
194
  channel channel
190
195
  webhook_url https://hooks.slack.com/services/XXX/XXX/XXX
191
196
  ])
197
+ assert_equal Fluent::SlackClient::IncomingWebhook, d.instance.slack.class
192
198
  time = Time.parse("2014-01-01 22:00:00 UTC").to_i
193
199
  d.tag = 'test'
194
200
  mock(d.instance.slack).post_message({
@@ -211,11 +217,37 @@ class SlackOutputTest < Test::Unit::TestCase
211
217
  end
212
218
  end
213
219
 
220
+ def test_default_slackbot
221
+ d = create_driver(%[
222
+ channel channel
223
+ slackbot_url https://xxxxx.slack.com/services/hooks/slackbot?token=XXXXXXX
224
+ ])
225
+ assert_equal Fluent::SlackClient::Slackbot, d.instance.slack.class
226
+ time = Time.parse("2014-01-01 22:00:00 UTC").to_i
227
+ d.tag = 'test'
228
+ mock(d.instance.slack).post_message({
229
+ channel: '#channel',
230
+ username: 'fluentd',
231
+ icon_emoji: ':question:',
232
+ attachments: [{
233
+ color: 'good',
234
+ fallback: "sowawa1\nsowawa2\n",
235
+ text: "sowawa1\nsowawa2\n",
236
+ }]
237
+ }, {})
238
+ with_timezone('Asia/Tokyo') do
239
+ d.emit({message: 'sowawa1'}, time)
240
+ d.emit({message: 'sowawa2'}, time)
241
+ d.run
242
+ end
243
+ end
244
+
214
245
  def test_default_slack_api
215
246
  d = create_driver(%[
216
247
  channel channel
217
248
  token XX-XX-XX
218
249
  ])
250
+ assert_equal Fluent::SlackClient::WebApi, d.instance.slack.class
219
251
  time = Time.parse("2014-01-01 22:00:00 UTC").to_i
220
252
  d.tag = 'test'
221
253
  mock(d.instance.slack).post_message({
@@ -13,7 +13,7 @@ require 'webrick/httpproxy'
13
13
  # TOKEN=XXXXX
14
14
  #
15
15
  Dotenv.load
16
- if ENV['WEBHOOK_URL'] and ENV['TOKEN']
16
+ if ENV['WEBHOOK_URL'] and ENV['SLACKBOT_URL'] and ENV['TOKEN']
17
17
 
18
18
  class TestProxyServer
19
19
  def initialize
@@ -49,11 +49,14 @@ if ENV['WEBHOOK_URL'] and ENV['TOKEN']
49
49
  class SlackClientTest < Test::Unit::TestCase
50
50
  def setup
51
51
  super
52
- @proxy = TestProxyServer.new.tap {|proxy| proxy.start }
53
- @incoming_webhook = Fluent::SlackClient::IncomingWebhook.new(ENV['WEBHOOK_URL'])
54
- @api = Fluent::SlackClient::WebApi.new
55
- @incoming_webhook_proxy = Fluent::SlackClient::IncomingWebhook.new(ENV['WEBHOOK_URL'], @proxy.proxy_url)
56
- @api_proxy = Fluent::SlackClient::WebApi.new(nil, @proxy.proxy_url)
52
+ @incoming = Fluent::SlackClient::IncomingWebhook.new(ENV['WEBHOOK_URL'])
53
+ @slackbot = Fluent::SlackClient::Slackbot.new(ENV['SLACKBOT_URL'])
54
+ @api = Fluent::SlackClient::WebApi.new
55
+
56
+ @proxy = TestProxyServer.new.tap {|proxy| proxy.start }
57
+ @incoming_proxy = Fluent::SlackClient::IncomingWebhook.new(ENV['WEBHOOK_URL'], @proxy.proxy_url)
58
+ @slackbot_proxy = Fluent::SlackClient::Slackbot.new(ENV['SLACKBOT_URL'], @proxy.proxy_url)
59
+ @api_proxy = Fluent::SlackClient::WebApi.new(nil, @proxy.proxy_url)
57
60
 
58
61
  @icon_url = 'http://www.google.com/s2/favicons?domain=www.google.de'
59
62
  end
@@ -63,11 +66,11 @@ if ENV['WEBHOOK_URL'] and ENV['TOKEN']
63
66
  end
64
67
 
65
68
  def token(client)
66
- client.is_a?(Fluent::SlackClient::WebApi) ? {token: ENV['TOKEN']} : {}
69
+ client.is_a?(Fluent::SlackClient::IncomingWebhook) ? {} : {token: ENV['TOKEN']}
67
70
  end
68
71
 
69
72
  def test_post_message_text
70
- [@incoming_webhook, @api, @incoming_webhook_proxy, @api_proxy].each do |slack|
73
+ [@incoming, @slackbot, @api].each do |slack|
71
74
  assert_nothing_raised do
72
75
  slack.post_message(
73
76
  {
@@ -86,7 +89,7 @@ if ENV['WEBHOOK_URL'] and ENV['TOKEN']
86
89
  end
87
90
 
88
91
  def test_post_message_fields
89
- [@incoming_webhook, @api, @incoming_webhook_proxy, @api_proxy].each do |slack|
92
+ [@incoming, @slackbot, @api].each do |slack|
90
93
  assert_nothing_raised do
91
94
  slack.post_message(
92
95
  {
@@ -113,8 +116,27 @@ if ENV['WEBHOOK_URL'] and ENV['TOKEN']
113
116
  end
114
117
  end
115
118
 
119
+ def test_post_via_proxy
120
+ [@incoming_proxy, @slackbot_proxy, @api_proxy].each do |slack|
121
+ assert_nothing_raised do
122
+ slack.post_message(
123
+ {
124
+ channel: '#general',
125
+ username: 'fluentd',
126
+ icon_emoji: ':question:',
127
+ attachments: [{
128
+ color: 'good',
129
+ fallback: "sowawa1\nsowawa2\n",
130
+ text: "sowawa1\nsowawa2\n",
131
+ }]
132
+ }.merge(token(slack))
133
+ )
134
+ end
135
+ end
136
+ end
137
+
116
138
  def test_post_message_icon_url
117
- [@incoming_webhook, @api].each do |slack|
139
+ [@incoming, @api].each do |slack|
118
140
  assert_nothing_raised do
119
141
  slack.post_message(
120
142
  {
@@ -133,7 +155,7 @@ if ENV['WEBHOOK_URL'] and ENV['TOKEN']
133
155
  end
134
156
 
135
157
  def test_post_message_text_mrkdwn
136
- [@incoming_webhook, @api].each do |slack|
158
+ [@incoming, @api].each do |slack|
137
159
  assert_nothing_raised do
138
160
  slack.post_message(
139
161
  {
@@ -152,7 +174,7 @@ if ENV['WEBHOOK_URL'] and ENV['TOKEN']
152
174
  end
153
175
 
154
176
  def test_post_message_fields_mrkdwn
155
- [@incoming_webhook, @api].each do |slack|
177
+ [@incoming, @api].each do |slack|
156
178
  assert_nothing_raised do
157
179
  slack.post_message(
158
180
  {
@@ -181,7 +203,7 @@ if ENV['WEBHOOK_URL'] and ENV['TOKEN']
181
203
  begin
182
204
  @api.channels_create(
183
205
  {
184
- name: '#foo',
206
+ name: '#test_channels_create',
185
207
  }.merge(token(@api))
186
208
  )
187
209
  rescue Fluent::SlackClient::NameTakenError
@@ -194,7 +216,25 @@ if ENV['WEBHOOK_URL'] and ENV['TOKEN']
194
216
  assert_nothing_raised do
195
217
  @api.post_message(
196
218
  {
197
- channel: '#bar',
219
+ channel: '#test_auto_api',
220
+ username: 'fluentd',
221
+ icon_emoji: ':question:',
222
+ attachments: [{
223
+ color: 'good',
224
+ fallback: "bar\n",
225
+ text: "bar\n",
226
+ }]
227
+ }.merge(token(@api)),
228
+ {
229
+ auto_channels_create: true,
230
+ }
231
+ )
232
+ end
233
+
234
+ assert_nothing_raised do
235
+ @slackbot.post_message(
236
+ {
237
+ channel: '#test_auto_slackbot',
198
238
  username: 'fluentd',
199
239
  icon_emoji: ':question:',
200
240
  attachments: [{
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fluent-plugin-slack
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.4
4
+ version: 0.5.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Keisuke SOGAWA
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2015-03-31 00:00:00.000000000 Z
12
+ date: 2015-04-01 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: fluentd