fluent-plugin-slack 0.5.4 → 0.5.5

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 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