pechkin 1.3.1 → 1.6.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/lib/pechkin/app/app.rb +2 -1
- data/lib/pechkin/app/app_builder.rb +2 -4
- data/lib/pechkin/app/request_handler.rb +1 -1
- data/lib/pechkin/auth.rb +1 -0
- data/lib/pechkin/{configuration/model.rb → bot.rb} +0 -1
- data/lib/pechkin/channel.rb +1 -57
- data/lib/pechkin/cli.rb +1 -1
- data/lib/pechkin/command/base.rb +1 -1
- data/lib/pechkin/command/send_data.rb +1 -0
- data/lib/pechkin/configuration/configuration_loader.rb +3 -3
- data/lib/pechkin/configuration/configuration_loader_bots.rb +1 -3
- data/lib/pechkin/configuration/configuration_loader_channels.rb +40 -5
- data/lib/pechkin/configuration/configuration_loader_views.rb +1 -3
- data/lib/pechkin/configuration.rb +0 -1
- data/lib/pechkin/connector/base.rb +29 -0
- data/lib/pechkin/connector/slack.rb +32 -0
- data/lib/pechkin/connector/telegram.rb +28 -0
- data/lib/pechkin/connector.rb +3 -23
- data/lib/pechkin/exceptions.rb +3 -0
- data/lib/pechkin/handler.rb +5 -10
- data/lib/pechkin/message.rb +64 -0
- data/lib/pechkin/message_matcher.rb +32 -16
- data/lib/pechkin/prometheus_utils.rb +6 -0
- data/lib/pechkin/version.rb +1 -1
- data/lib/pechkin.rb +5 -5
- metadata +14 -12
- data/lib/pechkin/connector_slack.rb +0 -28
- data/lib/pechkin/connector_telegram.rb +0 -24
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: fdd7554b43eb0ac8fa2fdb0fc9d02d31b4acf859c1b66a9470c1fdbd524d0eef
|
4
|
+
data.tar.gz: 224c8857ae9b72a48b91c8342040f9866d30a285a7f6e8595bf27e577f44bdd2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 623290e1546eb766cb844203af31e8f3708016199f4b1771e55c468cb252d21ecd874452795dc8417ba06d3be3e096526b3ad6c96826c84fa8189cd522e61ecb
|
7
|
+
data.tar.gz: 61f56c2dc2b400cced01460d977ee216d2c546be2935b3ce968f08c0266ad29d5915b14ea6601ca31f1497f984aba8eb9a1b4cfbbb27f5d358d44f49bd81157a
|
data/lib/pechkin/app/app.rb
CHANGED
@@ -34,9 +34,10 @@ module Pechkin
|
|
34
34
|
response(err.code, data)
|
35
35
|
end
|
36
36
|
|
37
|
-
def process_unhandled_error(
|
37
|
+
def process_unhandled_error(req, err)
|
38
38
|
data = { status: 'error', message: err.message }
|
39
39
|
logger.error("#{err.message}\n\t" + err.backtrace.join("\n\t"))
|
40
|
+
logger.error(req.body.read)
|
40
41
|
response(503, data)
|
41
42
|
end
|
42
43
|
end
|
@@ -15,9 +15,7 @@ module Pechkin
|
|
15
15
|
use Prometheus::Middleware::Collector, registry: prometheus
|
16
16
|
# Add Auth check if found htpasswd file or it was excplicitly provided
|
17
17
|
# See CLI class for configuration details
|
18
|
-
if options.htpasswd
|
19
|
-
use Pechkin::Auth::Middleware, auth_file: options.htpasswd
|
20
|
-
end
|
18
|
+
use Pechkin::Auth::Middleware, auth_file: options.htpasswd if options.htpasswd
|
21
19
|
use Prometheus::Middleware::Exporter, registry: prometheus
|
22
20
|
|
23
21
|
run app
|
@@ -34,7 +32,7 @@ module Pechkin
|
|
34
32
|
file = File.open(log_file, File::WRONLY | File::APPEND)
|
35
33
|
Logger.new(file)
|
36
34
|
else
|
37
|
-
Logger.new(
|
35
|
+
Logger.new($stdout)
|
38
36
|
end
|
39
37
|
end
|
40
38
|
end
|
@@ -2,7 +2,7 @@ module Pechkin
|
|
2
2
|
# Http requests handler. We need fresh instance per each request. To keep
|
3
3
|
# internal state isolated
|
4
4
|
class RequestHandler
|
5
|
-
REQ_PATH_PATTERN = %r{^/(.+)/([^/]+)/?$}
|
5
|
+
REQ_PATH_PATTERN = %r{^/(.+)/([^/]+)/?$}.freeze
|
6
6
|
|
7
7
|
attr_reader :req, :handler,
|
8
8
|
:channel_id, :message_id,
|
data/lib/pechkin/auth.rb
CHANGED
data/lib/pechkin/channel.rb
CHANGED
@@ -1,59 +1,3 @@
|
|
1
1
|
module Pechkin
|
2
|
-
|
3
|
-
class Chanel
|
4
|
-
attr_accessor :logger
|
5
|
-
|
6
|
-
def initialize(connector, channel_list, logger = ::Logger.new(STDOUT))
|
7
|
-
@connector = connector
|
8
|
-
@channel_list = channel_list
|
9
|
-
@channel_list = [channel_list] unless channel_list.is_a?(Array)
|
10
|
-
@logger = logger
|
11
|
-
end
|
12
|
-
|
13
|
-
def send_message(message, data, message_desc)
|
14
|
-
text = message.nil? ? '' : Message.new(data).render(message)
|
15
|
-
|
16
|
-
message_desc = substitute(data, message_desc)
|
17
|
-
|
18
|
-
logger.warn 'Resulting text is empty' if text.empty?
|
19
|
-
results = @channel_list.map do |id|
|
20
|
-
@connector.send_message(id, text, message_desc)
|
21
|
-
end
|
22
|
-
|
23
|
-
process_results(message, results)
|
24
|
-
end
|
25
|
-
|
26
|
-
private
|
27
|
-
|
28
|
-
def substitute(data, message_desc)
|
29
|
-
substitute_recursive(Substitute.new(data), message_desc)
|
30
|
-
end
|
31
|
-
|
32
|
-
def substitute_recursive(substitutions, object)
|
33
|
-
case object
|
34
|
-
when String
|
35
|
-
substitutions.process(object)
|
36
|
-
when Array
|
37
|
-
object.map { |o| substitute_recursive(substitutions, o) }
|
38
|
-
when Hash
|
39
|
-
r = {}
|
40
|
-
object.each { |k, v| r[k] = substitute_recursive(substitutions, v) }
|
41
|
-
r
|
42
|
-
else
|
43
|
-
object
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
def process_results(message, results)
|
48
|
-
success, error = results.partition { |_chat, code, _body| code < 400 }
|
49
|
-
error.each do |chat, code, body|
|
50
|
-
logger.error "#{message} => #{chat}[HTTP #{code}]: #{body}"
|
51
|
-
end
|
52
|
-
|
53
|
-
{
|
54
|
-
successful: success.map(&:first),
|
55
|
-
errors: error
|
56
|
-
}
|
57
|
-
end
|
58
|
-
end
|
2
|
+
Channel = Struct.new(:chat_ids, :connector, :messages, keyword_init: true)
|
59
3
|
end
|
data/lib/pechkin/cli.rb
CHANGED
@@ -13,7 +13,7 @@ module Pechkin
|
|
13
13
|
# @opt names [Array<String>] list of command line keys
|
14
14
|
# @opt desc [String] option description
|
15
15
|
# @opt type [Class] argument type to parse from command line, e.g. Integer
|
16
|
-
def opt(name, default: nil,
|
16
|
+
def opt(name, names:, default: nil, desc: '', type: nil)
|
17
17
|
@cli_options ||= []
|
18
18
|
|
19
19
|
# raise ':names is nil or empty' if names.nil? || names.empty?
|
data/lib/pechkin/command/base.rb
CHANGED
@@ -9,7 +9,7 @@ module Pechkin
|
|
9
9
|
# command behaviour
|
10
10
|
# @opt stdout [IO] IO object which represents STDOUT
|
11
11
|
# @opt stderr [IO] IO object which represents STDERR
|
12
|
-
def initialize(options, stdout:
|
12
|
+
def initialize(options, stdout: $stdout, stderr: $stderr)
|
13
13
|
@options = options
|
14
14
|
@stdout = stdout
|
15
15
|
@stderr = stderr
|
@@ -13,11 +13,11 @@ module Pechkin
|
|
13
13
|
def create_connector(bot)
|
14
14
|
case bot.connector
|
15
15
|
when 'tg', 'telegram'
|
16
|
-
|
16
|
+
Connector::Telegram.new(bot.token, bot.name)
|
17
17
|
when 'slack'
|
18
|
-
|
18
|
+
Connector::Slack.new(bot.token, bot.name)
|
19
19
|
else
|
20
|
-
raise
|
20
|
+
raise "Unknown connector #{bot.connector} for #{bot.name}"
|
21
21
|
end
|
22
22
|
end
|
23
23
|
|
@@ -15,9 +15,7 @@ module Pechkin
|
|
15
15
|
def load_bots_configuration(working_dir, bots)
|
16
16
|
bots_dir = File.join(working_dir, 'bots')
|
17
17
|
|
18
|
-
unless File.directory?(bots_dir)
|
19
|
-
raise ConfigurationError, "'#{bots_dir}' is not a directory"
|
20
|
-
end
|
18
|
+
raise ConfigurationError, "'#{bots_dir}' is not a directory" unless File.directory?(bots_dir)
|
21
19
|
|
22
20
|
Dir["#{bots_dir}/*.yml"].each do |bot_file|
|
23
21
|
name = File.basename(bot_file, '.yml')
|
@@ -22,9 +22,7 @@ module Pechkin
|
|
22
22
|
def load_channels_configuration(working_dir, channels)
|
23
23
|
channels_dir = File.join(working_dir, 'channels')
|
24
24
|
|
25
|
-
unless File.directory?(channels_dir)
|
26
|
-
raise ConfigurationError, "'#{channels_dir}' is not a directory"
|
27
|
-
end
|
25
|
+
raise ConfigurationError, "'#{channels_dir}' is not a directory" unless File.directory?(channels_dir)
|
28
26
|
|
29
27
|
Dir["#{channels_dir}/*"].each do |channel_dir|
|
30
28
|
next unless File.directory?(channel_dir)
|
@@ -63,16 +61,53 @@ module Pechkin
|
|
63
61
|
message_config = YAML.safe_load(IO.read(file))
|
64
62
|
name = File.basename(file, '.yml')
|
65
63
|
|
64
|
+
# Dirty workaround. I need to recursively load templates. When doing it
|
65
|
+
# we looking for {'template': '...path to template..' } objects. But we
|
66
|
+
# don't want to force user write something like:
|
67
|
+
# text:
|
68
|
+
# template: '... path to main template...'
|
69
|
+
# because it's too mouthful for such common case.
|
70
|
+
#
|
71
|
+
# So now we pull main template out, then load everyting else. Then put
|
72
|
+
# it back.
|
73
|
+
template = nil
|
66
74
|
if message_config.key?('template')
|
67
|
-
|
75
|
+
template = get_template(message_config['template'])
|
76
|
+
message_config.delete('template')
|
68
77
|
end
|
69
78
|
|
70
|
-
|
79
|
+
message_config = load_templates(message_config)
|
80
|
+
message_config['template'] = template unless template.nil?
|
81
|
+
|
82
|
+
messages[name] = Message.new(message_config)
|
71
83
|
end
|
72
84
|
|
73
85
|
messages
|
74
86
|
end
|
75
87
|
|
88
|
+
def load_templates(object)
|
89
|
+
case object
|
90
|
+
when String
|
91
|
+
object
|
92
|
+
when Array
|
93
|
+
object.map { |o| load_templates(o) }
|
94
|
+
when Hash
|
95
|
+
if object.key?('template')
|
96
|
+
msg = 'When using template only 1 KV pair allowed'
|
97
|
+
raise ConfigurationError, msg unless object.size == 1
|
98
|
+
|
99
|
+
# Replace whole object with created template.
|
100
|
+
get_template(object['template'])
|
101
|
+
else
|
102
|
+
r = {}
|
103
|
+
object.each { |k, v| r[k] = load_templates(v) }
|
104
|
+
r
|
105
|
+
end
|
106
|
+
else
|
107
|
+
object
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
76
111
|
def get_template(path)
|
77
112
|
msg = "Can't find template: #{path}"
|
78
113
|
raise ConfigurationError, msg unless @views.key?(path)
|
@@ -15,9 +15,7 @@ module Pechkin
|
|
15
15
|
def load_views_configuration(working_dir, views)
|
16
16
|
views_dir = File.join(working_dir, 'views')
|
17
17
|
|
18
|
-
unless File.directory?(views_dir)
|
19
|
-
raise ConfigurationError, "'#{views_dir}' is not a directory"
|
20
|
-
end
|
18
|
+
raise ConfigurationError, "'#{views_dir}' is not a directory" unless File.directory?(views_dir)
|
21
19
|
|
22
20
|
Dir["#{views_dir}/**/*.erb"].each do |f|
|
23
21
|
relative_path = f["#{views_dir}/".length..-1]
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Pechkin
|
2
|
+
module Connector
|
3
|
+
# Base connector
|
4
|
+
class Base
|
5
|
+
DEFAULT_HEADERS = {
|
6
|
+
'Content-Type' => 'application/json; charset=UTF-8'
|
7
|
+
}.freeze
|
8
|
+
|
9
|
+
def send_message(chat, message, message_desc); end
|
10
|
+
|
11
|
+
def preview(chats, message, _message_desc)
|
12
|
+
"Connector: #{self.class.name}; Chats: #{chats.join(', ')}\n" \
|
13
|
+
"Message:\n#{message}"
|
14
|
+
end
|
15
|
+
|
16
|
+
def post_data(url, data, headers: {})
|
17
|
+
uri = URI.parse(url)
|
18
|
+
headers = DEFAULT_HEADERS.merge(headers)
|
19
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
20
|
+
http.use_ssl = url.start_with?('https://')
|
21
|
+
|
22
|
+
request = Net::HTTP::Post.new(uri.request_uri, headers)
|
23
|
+
request.body = data.to_json
|
24
|
+
|
25
|
+
http.request(request)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Pechkin
|
2
|
+
module Connector
|
3
|
+
class Slack < Connector::Base # :nodoc:
|
4
|
+
attr_reader :name
|
5
|
+
|
6
|
+
def initialize(bot_token, name)
|
7
|
+
super()
|
8
|
+
|
9
|
+
@headers = { 'Authorization' => "Bearer #{bot_token}" }
|
10
|
+
@name = name
|
11
|
+
end
|
12
|
+
|
13
|
+
def send_message(channel, message, message_desc)
|
14
|
+
text = CGI.unescape_html(message)
|
15
|
+
|
16
|
+
attachments = message_desc['slack_attachments'] || {}
|
17
|
+
|
18
|
+
if text.strip.empty? && attachments.empty?
|
19
|
+
return { channel: channel, code: 400,
|
20
|
+
response: 'Internal error: message is empty' }
|
21
|
+
end
|
22
|
+
|
23
|
+
params = { channel: channel, text: text, attachments: attachments }
|
24
|
+
|
25
|
+
url = 'https://slack.com/api/chat.postMessage'
|
26
|
+
response = post_data(url, params, headers: @headers)
|
27
|
+
|
28
|
+
{ channel: channel, code: response.code.to_i, response: response.body }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Pechkin
|
2
|
+
module Connector
|
3
|
+
class Telegram < Connector::Base # :nodoc:
|
4
|
+
attr_reader :name
|
5
|
+
|
6
|
+
def initialize(bot_token, name)
|
7
|
+
super()
|
8
|
+
|
9
|
+
@bot_token = bot_token
|
10
|
+
@name = name
|
11
|
+
end
|
12
|
+
|
13
|
+
def send_message(chat_id, message, message_desc)
|
14
|
+
options = { parse_mode: message_desc['telegram_parse_mode'] || 'HTML' }
|
15
|
+
params = options.update(chat_id: chat_id, text: message)
|
16
|
+
|
17
|
+
response = post_data(method_url('sendMessage'), params)
|
18
|
+
{ chat_id: chat_id, code: response.code.to_i, response: response.body }
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def method_url(method)
|
24
|
+
"https://api.telegram.org/bot#{@bot_token}/#{method}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/pechkin/connector.rb
CHANGED
@@ -4,26 +4,6 @@ require 'uri'
|
|
4
4
|
require 'json'
|
5
5
|
require 'cgi'
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
def send_message(chat, message, message_desc); end
|
11
|
-
|
12
|
-
def preview(chats, message, _message_desc)
|
13
|
-
"Connector: #{self.class.name}; Chats: #{chats.join(', ')}\n" \
|
14
|
-
"Message:\n#{message}"
|
15
|
-
end
|
16
|
-
|
17
|
-
def post_data(url, data, headers: {})
|
18
|
-
uri = URI.parse(url)
|
19
|
-
headers = { 'Content-Type' => 'application/json' }.merge(headers)
|
20
|
-
http = Net::HTTP.new(uri.host, uri.port)
|
21
|
-
http.use_ssl = url.start_with?('https://')
|
22
|
-
|
23
|
-
request = Net::HTTP::Post.new(uri.request_uri, headers)
|
24
|
-
request.body = data.to_json
|
25
|
-
|
26
|
-
http.request(request)
|
27
|
-
end
|
28
|
-
end
|
29
|
-
end
|
7
|
+
require_relative 'connector/base'
|
8
|
+
require_relative 'connector/slack'
|
9
|
+
require_relative 'connector/telegram'
|
data/lib/pechkin/exceptions.rb
CHANGED
data/lib/pechkin/handler.rb
CHANGED
@@ -5,13 +5,13 @@ module Pechkin
|
|
5
5
|
attr_reader :channels, :message_matcher
|
6
6
|
attr_accessor :logger
|
7
7
|
|
8
|
-
def initialize(channels, stdout =
|
8
|
+
def initialize(channels, stdout = $stdout, stderr = $stderr)
|
9
9
|
@channels = channels
|
10
10
|
# Create empty logger by default
|
11
11
|
@logger = Logger.new(IO::NULL)
|
12
12
|
@stdout = stdout
|
13
13
|
@stderr = stderr
|
14
|
-
@message_matcher = MessageMatcher.new
|
14
|
+
@message_matcher = MessageMatcher.new(@logger)
|
15
15
|
end
|
16
16
|
|
17
17
|
# Handles message request. Each request has three parameters: channel id,
|
@@ -35,7 +35,7 @@ module Pechkin
|
|
35
35
|
if message_allowed?(message_config, data)
|
36
36
|
chats.map { |chat| connector.send_message(chat, text, message_config) }
|
37
37
|
else
|
38
|
-
logger.
|
38
|
+
logger.warn "#{channel_id}/#{msg_id}: " \
|
39
39
|
"Skip sending message. Because it's not allowed"
|
40
40
|
[]
|
41
41
|
end
|
@@ -95,13 +95,8 @@ module Pechkin
|
|
95
95
|
def prepare_message(channel_id, msg_id, data)
|
96
96
|
channel_config = fetch_channel(channel_id)
|
97
97
|
# Find message and try substitute values to message parameters.
|
98
|
-
|
99
|
-
|
100
|
-
data = (message_config['variables'] || {}).merge(data)
|
101
|
-
template = message_config['template']
|
102
|
-
|
103
|
-
text = ''
|
104
|
-
text = template.render(data) unless template.nil?
|
98
|
+
message = fetch_message(channel_config, msg_id)
|
99
|
+
message_config, text = message.prepare(data)
|
105
100
|
|
106
101
|
[channel_config, message_config, text]
|
107
102
|
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Pechkin
|
2
|
+
# Message object
|
3
|
+
#
|
4
|
+
# TBD
|
5
|
+
class Message
|
6
|
+
def initialize(message)
|
7
|
+
@message = message
|
8
|
+
end
|
9
|
+
|
10
|
+
def prepare(data)
|
11
|
+
data = (@message['variables'] || {}).merge(data)
|
12
|
+
# Find message and try substitute values to message parameters.
|
13
|
+
message_config = render(data, substitute(data, @message))
|
14
|
+
text = ''
|
15
|
+
text = message_config.delete('template') if message_config.key?('template')
|
16
|
+
|
17
|
+
[message_config, text]
|
18
|
+
end
|
19
|
+
|
20
|
+
def to_h
|
21
|
+
Marshal.load(Marshal.dump(@message))
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def substitute(data, message_desc)
|
27
|
+
substitute_recursive(Substitute.new(data), message_desc)
|
28
|
+
end
|
29
|
+
|
30
|
+
def substitute_recursive(substitutions, object)
|
31
|
+
case object
|
32
|
+
when String
|
33
|
+
substitutions.process(object)
|
34
|
+
when Array
|
35
|
+
object.map { |o| substitute_recursive(substitutions, o) }
|
36
|
+
when Hash
|
37
|
+
r = {}
|
38
|
+
object.each { |k, v| r[k] = substitute_recursive(substitutions, v) }
|
39
|
+
r
|
40
|
+
else
|
41
|
+
object
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def render(data, message_desc)
|
46
|
+
render_recursive(data, message_desc)
|
47
|
+
end
|
48
|
+
|
49
|
+
def render_recursive(data, object)
|
50
|
+
case object
|
51
|
+
when MessageTemplate
|
52
|
+
object.render(data)
|
53
|
+
when Array
|
54
|
+
object.map { |o| render_recursive(data, o) }
|
55
|
+
when Hash
|
56
|
+
r = {}
|
57
|
+
object.each { |k, v| r[k] = render_recursive(data, v) }
|
58
|
+
r
|
59
|
+
else
|
60
|
+
object
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
module Pechkin
|
2
2
|
class MessageMatchError < StandardError; end
|
3
|
+
|
3
4
|
# Allows to match message configuration against received data.
|
4
5
|
#
|
5
6
|
# Data is checked againts either allow or forbid rules. But not both at the
|
@@ -10,6 +11,13 @@ module Pechkin
|
|
10
11
|
class MessageMatcher
|
11
12
|
KEY_ALLOW = 'allow'.freeze
|
12
13
|
KEY_FORBID = 'forbid'.freeze
|
14
|
+
|
15
|
+
attr_reader :logger
|
16
|
+
|
17
|
+
def initialize(logger)
|
18
|
+
@logger = logger
|
19
|
+
end
|
20
|
+
|
13
21
|
# Checks data object against allow / forbid rule sets in message
|
14
22
|
# configuration. If data object matches rules it means we can process this
|
15
23
|
# data and send message.
|
@@ -23,10 +31,10 @@ module Pechkin
|
|
23
31
|
|
24
32
|
if message_config.key?(KEY_ALLOW)
|
25
33
|
rules = message_config[KEY_ALLOW]
|
26
|
-
rules.any? { |r| check_rule(r, data) }
|
34
|
+
rules.any? { |r| check_rule(r, r, data) }
|
27
35
|
elsif message_config.key?(KEY_FORBID)
|
28
36
|
rules = message_config[KEY_FORBID]
|
29
|
-
rules.all? { |r| !check_rule(r, data) }
|
37
|
+
rules.all? { |r| !check_rule(r, r, data) }
|
30
38
|
else
|
31
39
|
true
|
32
40
|
end
|
@@ -43,35 +51,43 @@ module Pechkin
|
|
43
51
|
# Check rule object against data. Rules are checked recursievly, i.e. we
|
44
52
|
# can take one field in rule and check it separately as new rule. If all
|
45
53
|
# fields are passed check then whole rule passed.
|
46
|
-
def check_rule(
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
54
|
+
def check_rule(top_rule, sub_rule, data)
|
55
|
+
result = case sub_rule
|
56
|
+
when Hash
|
57
|
+
check_hash_rule(top_rule, sub_rule, data)
|
58
|
+
when Array
|
59
|
+
check_array_rule(top_rule, sub_rule, data)
|
60
|
+
when String
|
61
|
+
check_string_rule(top_rule, sub_rule, data)
|
62
|
+
else
|
63
|
+
sub_rule.eql?(data)
|
64
|
+
end
|
65
|
+
|
66
|
+
unless result
|
67
|
+
logger.info "Expected #{sub_rule.to_json} got #{data.to_json} when" \
|
68
|
+
" checking #{top_rule.to_json}"
|
55
69
|
end
|
70
|
+
|
71
|
+
result
|
56
72
|
end
|
57
73
|
|
58
|
-
def check_hash_rule(hash, data)
|
74
|
+
def check_hash_rule(top_rule, hash, data)
|
59
75
|
return false unless data.is_a?(Hash)
|
60
76
|
|
61
77
|
hash.all? do |key, value|
|
62
|
-
data.key?(key) && check_rule(value, data[key])
|
78
|
+
data.key?(key) && check_rule(top_rule, value, data[key])
|
63
79
|
end
|
64
80
|
end
|
65
81
|
|
66
|
-
def check_array_rule(array, data)
|
82
|
+
def check_array_rule(top_rule, array, data)
|
67
83
|
return false unless data.is_a?(Array)
|
68
84
|
|
69
85
|
# Deep array check needs to be done against all elements so we zip arrays
|
70
86
|
# to pair each rule with data element
|
71
|
-
array.zip(data).all? { |r, d| check_rule(r, d) }
|
87
|
+
array.zip(data).all? { |r, d| check_rule(top_rule, r, d) }
|
72
88
|
end
|
73
89
|
|
74
|
-
def check_string_rule(str, data)
|
90
|
+
def check_string_rule(_top_rule, str, data)
|
75
91
|
str.eql? data
|
76
92
|
end
|
77
93
|
end
|
@@ -5,6 +5,12 @@ module Pechkin
|
|
5
5
|
registry = ::Prometheus::Client.registry
|
6
6
|
registry.gauge(:pechkin_start_time_seconds,
|
7
7
|
docstring: 'Startup timestamp').set(Time.now.to_i)
|
8
|
+
|
9
|
+
version_labels = { version: Pechkin::Version.version_string }
|
10
|
+
registry.gauge(:pechkin_version,
|
11
|
+
docstring: 'Pechkin version', labels: [:version])
|
12
|
+
.set(1, labels: version_labels)
|
13
|
+
|
8
14
|
registry
|
9
15
|
end
|
10
16
|
end
|
data/lib/pechkin/version.rb
CHANGED
data/lib/pechkin.rb
CHANGED
@@ -8,15 +8,15 @@ require 'htauth'
|
|
8
8
|
require 'base64'
|
9
9
|
|
10
10
|
require_relative 'pechkin/cli'
|
11
|
+
require_relative 'pechkin/bot'
|
12
|
+
require_relative 'pechkin/channel'
|
13
|
+
require_relative 'pechkin/message'
|
11
14
|
require_relative 'pechkin/command'
|
12
15
|
require_relative 'pechkin/exceptions'
|
13
16
|
require_relative 'pechkin/handler'
|
14
17
|
require_relative 'pechkin/message_matcher'
|
15
18
|
require_relative 'pechkin/message_template'
|
16
19
|
require_relative 'pechkin/connector'
|
17
|
-
require_relative 'pechkin/connector_slack'
|
18
|
-
require_relative 'pechkin/connector_telegram'
|
19
|
-
require_relative 'pechkin/channel'
|
20
20
|
require_relative 'pechkin/configuration'
|
21
21
|
require_relative 'pechkin/substitute'
|
22
22
|
require_relative 'pechkin/prometheus_utils'
|
@@ -30,8 +30,8 @@ module Pechkin # :nodoc:
|
|
30
30
|
cmd = Command::Dispatcher.new(options).dispatch
|
31
31
|
cmd.execute
|
32
32
|
rescue StandardError => e
|
33
|
-
warn
|
34
|
-
warn "\t
|
33
|
+
warn "Error: #{e.message}"
|
34
|
+
warn "\t#{e.backtrace.reverse.join("\n\t")}" if options.debug?
|
35
35
|
exit 2
|
36
36
|
end
|
37
37
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pechkin
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ilya Arkhanhelsky
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-10-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: htauth
|
@@ -16,14 +16,14 @@ dependencies:
|
|
16
16
|
requirements:
|
17
17
|
- - '='
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: 2.
|
19
|
+
version: 2.1.1
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
24
|
- - '='
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: 2.
|
26
|
+
version: 2.1.1
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
28
|
name: powerpack
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -58,14 +58,14 @@ dependencies:
|
|
58
58
|
requirements:
|
59
59
|
- - '='
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: 2.
|
61
|
+
version: 2.2.3
|
62
62
|
type: :runtime
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
66
|
- - '='
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version: 2.
|
68
|
+
version: 2.2.3
|
69
69
|
description:
|
70
70
|
email: ilya.arkhanhelsky at gmail.com
|
71
71
|
executables:
|
@@ -81,6 +81,7 @@ files:
|
|
81
81
|
- lib/pechkin/app/app_error.rb
|
82
82
|
- lib/pechkin/app/request_handler.rb
|
83
83
|
- lib/pechkin/auth.rb
|
84
|
+
- lib/pechkin/bot.rb
|
84
85
|
- lib/pechkin/channel.rb
|
85
86
|
- lib/pechkin/cli.rb
|
86
87
|
- lib/pechkin/command.rb
|
@@ -95,12 +96,13 @@ files:
|
|
95
96
|
- lib/pechkin/configuration/configuration_loader_bots.rb
|
96
97
|
- lib/pechkin/configuration/configuration_loader_channels.rb
|
97
98
|
- lib/pechkin/configuration/configuration_loader_views.rb
|
98
|
-
- lib/pechkin/configuration/model.rb
|
99
99
|
- lib/pechkin/connector.rb
|
100
|
-
- lib/pechkin/
|
101
|
-
- lib/pechkin/
|
100
|
+
- lib/pechkin/connector/base.rb
|
101
|
+
- lib/pechkin/connector/slack.rb
|
102
|
+
- lib/pechkin/connector/telegram.rb
|
102
103
|
- lib/pechkin/exceptions.rb
|
103
104
|
- lib/pechkin/handler.rb
|
105
|
+
- lib/pechkin/message.rb
|
104
106
|
- lib/pechkin/message_matcher.rb
|
105
107
|
- lib/pechkin/message_template.rb
|
106
108
|
- lib/pechkin/prometheus_utils.rb
|
@@ -116,16 +118,16 @@ require_paths:
|
|
116
118
|
- lib
|
117
119
|
required_ruby_version: !ruby/object:Gem::Requirement
|
118
120
|
requirements:
|
119
|
-
- - "
|
121
|
+
- - ">"
|
120
122
|
- !ruby/object:Gem::Version
|
121
|
-
version: '
|
123
|
+
version: '2.5'
|
122
124
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
123
125
|
requirements:
|
124
126
|
- - ">="
|
125
127
|
- !ruby/object:Gem::Version
|
126
128
|
version: '0'
|
127
129
|
requirements: []
|
128
|
-
rubygems_version: 3.
|
130
|
+
rubygems_version: 3.2.22
|
129
131
|
signing_key:
|
130
132
|
specification_version: 4
|
131
133
|
summary: Web service to proxy webhooks to Telegram Bot API
|
@@ -1,28 +0,0 @@
|
|
1
|
-
module Pechkin # :nodoc:
|
2
|
-
class SlackConnector < Connector # :nodoc:
|
3
|
-
attr_reader :name
|
4
|
-
|
5
|
-
def initialize(bot_token, name)
|
6
|
-
@headers = { 'Authorization' => "Bearer #{bot_token}" }
|
7
|
-
@name = name
|
8
|
-
end
|
9
|
-
|
10
|
-
def send_message(channel, message, message_desc)
|
11
|
-
text = CGI.unescape_html(message)
|
12
|
-
|
13
|
-
attachments = message_desc['slack_attachments'] || {}
|
14
|
-
|
15
|
-
if text.strip.empty? && attachments.empty?
|
16
|
-
return { channel: channel, code: 400,
|
17
|
-
response: 'Internal error: message is empty' }
|
18
|
-
end
|
19
|
-
|
20
|
-
params = { channel: channel, text: text, attachments: attachments }
|
21
|
-
|
22
|
-
url = 'https://slack.com/api/chat.postMessage'
|
23
|
-
response = post_data(url, params, headers: @headers)
|
24
|
-
|
25
|
-
{ channel: channel, code: response.code.to_i, response: response.body }
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
@@ -1,24 +0,0 @@
|
|
1
|
-
module Pechkin
|
2
|
-
class TelegramConnector < Connector #:nodoc:
|
3
|
-
attr_reader :name
|
4
|
-
|
5
|
-
def initialize(bot_token, name)
|
6
|
-
@bot_token = bot_token
|
7
|
-
@name = name
|
8
|
-
end
|
9
|
-
|
10
|
-
def send_message(chat_id, message, message_desc)
|
11
|
-
options = { parse_mode: message_desc['telegram_parse_mode'] || 'HTML' }
|
12
|
-
params = options.update(chat_id: chat_id, text: message)
|
13
|
-
|
14
|
-
response = post_data(method_url('sendMessage'), params)
|
15
|
-
{ chat_id: chat_id, code: response.code.to_i, response: response.body }
|
16
|
-
end
|
17
|
-
|
18
|
-
private
|
19
|
-
|
20
|
-
def method_url(method)
|
21
|
-
"https://api.telegram.org/bot#{@bot_token}/#{method}"
|
22
|
-
end
|
23
|
-
end
|
24
|
-
end
|