pechkin 1.2.2 → 1.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/pechkin.rb +1 -0
- data/lib/pechkin/app.rb +4 -114
- data/lib/pechkin/app/app.rb +43 -0
- data/lib/pechkin/app/app_builder.rb +41 -0
- data/lib/pechkin/app/app_error.rb +27 -0
- data/lib/pechkin/app/request_handler.rb +49 -0
- data/lib/pechkin/configuration.rb +4 -4
- data/lib/pechkin/connector_slack.rb +3 -2
- data/lib/pechkin/connector_telegram.rb +1 -1
- data/lib/pechkin/handler.rb +46 -24
- data/lib/pechkin/message_matcher.rb +78 -0
- data/lib/pechkin/version.rb +1 -1
- metadata +7 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 21153645eb45d32b113d2c95191e79ac5568a10829b3989ccc9f75da7b53bf9e
|
4
|
+
data.tar.gz: d80b4bbbeb7e4bdfeb251e358bb0a9c99d50eae96c5078b688bce5e12a3c73e3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 30e9654efb5c1a11c4816feb51fc494be1251e106a2fac682be9cb68738faec6e8b45c747329f90cff226ef1fe9df2c097bb0083c79d57d55b020982773dd0b8
|
7
|
+
data.tar.gz: 62d6113f5ef1aedfe36c3eb86bc619d7cbac2cd9e54a37f64cbd293b1856efe47ba572bdf25efcc2a20f7b1affbbffe8cc2e74ad9784091cc69f7fa232ebe19b
|
data/lib/pechkin.rb
CHANGED
@@ -11,6 +11,7 @@ require_relative 'pechkin/cli'
|
|
11
11
|
require_relative 'pechkin/command'
|
12
12
|
require_relative 'pechkin/exceptions'
|
13
13
|
require_relative 'pechkin/handler'
|
14
|
+
require_relative 'pechkin/message_matcher'
|
14
15
|
require_relative 'pechkin/message_template'
|
15
16
|
require_relative 'pechkin/connector'
|
16
17
|
require_relative 'pechkin/connector_slack'
|
data/lib/pechkin/app.rb
CHANGED
@@ -1,114 +1,4 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
def build(handler, options)
|
6
|
-
app = App.new
|
7
|
-
app.handler = handler
|
8
|
-
prometheus = Pechkin::PrometheusUtils.registry
|
9
|
-
logger = create_logger(options.log_dir)
|
10
|
-
|
11
|
-
Rack::Builder.app do
|
12
|
-
use Rack::CommonLogger, logger
|
13
|
-
use Rack::Deflater
|
14
|
-
use Prometheus::Middleware::Collector, registry: prometheus
|
15
|
-
# Add Auth check if found htpasswd file or it was excplicitly provided
|
16
|
-
# See CLI class for configuration details
|
17
|
-
if options.htpasswd
|
18
|
-
use Pechkin::Auth::Middleware, auth_file: options.htpasswd
|
19
|
-
end
|
20
|
-
use Prometheus::Middleware::Exporter, registry: prometheus
|
21
|
-
|
22
|
-
run app
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
private
|
27
|
-
|
28
|
-
def create_logger(log_dir)
|
29
|
-
if log_dir
|
30
|
-
raise "Directory #{log_dir} does not exist" unless File.exist?(log_dir)
|
31
|
-
|
32
|
-
log_file = File.join(log_dir, 'pechkin.log')
|
33
|
-
file = File.open(log_file, File::WRONLY | File::APPEND)
|
34
|
-
Logger.new(file)
|
35
|
-
else
|
36
|
-
Logger.new(STDOUT)
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
|
-
# Rack application to handle requests
|
42
|
-
class App
|
43
|
-
attr_accessor :handler
|
44
|
-
|
45
|
-
def call(env)
|
46
|
-
RequestHandler.new(handler, env).handle
|
47
|
-
rescue StandardError => e
|
48
|
-
body = { status: 'error', reason: e.message }.to_json
|
49
|
-
['503', { 'Content-Type' => 'application/json' }, [body]]
|
50
|
-
end
|
51
|
-
end
|
52
|
-
|
53
|
-
# Http requests handler. We need fresh instance per each request. To keep
|
54
|
-
# internal state isolated
|
55
|
-
class RequestHandler
|
56
|
-
REQ_PATH_PATTERN = %r{^/(.+)/([^/]+)/?$}
|
57
|
-
DEFAULT_CONTENT_TYPE = { 'Content-Type' => 'application/json' }.freeze
|
58
|
-
DEFAULT_HEADERS = {}.merge(DEFAULT_CONTENT_TYPE).freeze
|
59
|
-
|
60
|
-
attr_reader :req, :env, :handler,
|
61
|
-
:channel_id, :message_id
|
62
|
-
|
63
|
-
def initialize(handler, env)
|
64
|
-
@handler = handler
|
65
|
-
@env = env
|
66
|
-
@req = Rack::Request.new(env)
|
67
|
-
|
68
|
-
@channel_id, @message_id = req.path_info.match(REQ_PATH_PATTERN) do |m|
|
69
|
-
[m[1], m[2]]
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
def handle
|
74
|
-
return not_allowed unless post?
|
75
|
-
return not_found unless message?
|
76
|
-
|
77
|
-
begin
|
78
|
-
data = JSON.parse(req.body.read)
|
79
|
-
rescue JSON::JSONError => e
|
80
|
-
return bad_request(e.message)
|
81
|
-
end
|
82
|
-
|
83
|
-
response(200, handler.handle(channel_id, message_id, data).to_json)
|
84
|
-
end
|
85
|
-
|
86
|
-
private
|
87
|
-
|
88
|
-
def message?
|
89
|
-
return false unless @channel_id && @message_id
|
90
|
-
|
91
|
-
handler.message?(@channel_id, @message_id)
|
92
|
-
end
|
93
|
-
|
94
|
-
def not_allowed
|
95
|
-
response(405, '{"status":"error", "reason":"method not allowed"}')
|
96
|
-
end
|
97
|
-
|
98
|
-
def not_found
|
99
|
-
response(404, '{"status":"error", "reason":"message not found"}')
|
100
|
-
end
|
101
|
-
|
102
|
-
def bad_request(body)
|
103
|
-
response(503, body)
|
104
|
-
end
|
105
|
-
|
106
|
-
def response(code, body)
|
107
|
-
[code.to_s, DEFAULT_HEADERS, [body]]
|
108
|
-
end
|
109
|
-
|
110
|
-
def post?
|
111
|
-
req.post?
|
112
|
-
end
|
113
|
-
end
|
114
|
-
end
|
1
|
+
require_relative 'app/app_builder'
|
2
|
+
require_relative 'app/app_error'
|
3
|
+
require_relative 'app/app'
|
4
|
+
require_relative 'app/request_handler'
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Pechkin
|
2
|
+
# Rack application to handle requests
|
3
|
+
class App
|
4
|
+
DEFAULT_CONTENT_TYPE = { 'Content-Type' => 'application/json' }.freeze
|
5
|
+
DEFAULT_HEADERS = {}.merge(DEFAULT_CONTENT_TYPE).freeze
|
6
|
+
|
7
|
+
attr_accessor :handler, :logger
|
8
|
+
|
9
|
+
def initialize(logger)
|
10
|
+
@logger = logger
|
11
|
+
end
|
12
|
+
|
13
|
+
def call(env)
|
14
|
+
req = Rack::Request.new(env)
|
15
|
+
result = RequestHandler.new(handler, req, logger).handle
|
16
|
+
response(200, result)
|
17
|
+
rescue AppError => e
|
18
|
+
proces_app_error(req, e)
|
19
|
+
rescue StandardError => e
|
20
|
+
process_unhandled_error(req, e)
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def response(code, body)
|
26
|
+
[code.to_s, DEFAULT_HEADERS, [body.to_json]]
|
27
|
+
end
|
28
|
+
|
29
|
+
def proces_app_error(req, err)
|
30
|
+
data = { status: 'error', message: err.message }
|
31
|
+
req.body.rewind
|
32
|
+
body = req.body.read
|
33
|
+
logger.error "Can't process message: #{err.message}. Body: '#{body}'"
|
34
|
+
response(err.code, data)
|
35
|
+
end
|
36
|
+
|
37
|
+
def process_unhandled_error(_req, err)
|
38
|
+
data = { status: 'error', message: err.message }
|
39
|
+
logger.error("#{err.message}\n\t" + err.backtrace.join("\n\t"))
|
40
|
+
response(503, data)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Pechkin
|
2
|
+
# Application configurator and builder. This creates all needed middleware
|
3
|
+
# and stuff
|
4
|
+
class AppBuilder
|
5
|
+
def build(handler, options)
|
6
|
+
logger = create_logger(options.log_dir)
|
7
|
+
handler.logger = logger
|
8
|
+
app = App.new(logger)
|
9
|
+
app.handler = handler
|
10
|
+
prometheus = Pechkin::PrometheusUtils.registry
|
11
|
+
|
12
|
+
Rack::Builder.app do
|
13
|
+
use Rack::CommonLogger, logger
|
14
|
+
use Rack::Deflater
|
15
|
+
use Prometheus::Middleware::Collector, registry: prometheus
|
16
|
+
# Add Auth check if found htpasswd file or it was excplicitly provided
|
17
|
+
# See CLI class for configuration details
|
18
|
+
if options.htpasswd
|
19
|
+
use Pechkin::Auth::Middleware, auth_file: options.htpasswd
|
20
|
+
end
|
21
|
+
use Prometheus::Middleware::Exporter, registry: prometheus
|
22
|
+
|
23
|
+
run app
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def create_logger(log_dir)
|
30
|
+
if log_dir
|
31
|
+
raise "Directory #{log_dir} does not exist" unless File.exist?(log_dir)
|
32
|
+
|
33
|
+
log_file = File.join(log_dir, 'pechkin.log')
|
34
|
+
file = File.open(log_file, File::WRONLY | File::APPEND)
|
35
|
+
Logger.new(file)
|
36
|
+
else
|
37
|
+
Logger.new(STDOUT)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Pechkin
|
2
|
+
# Generic application error class.
|
3
|
+
#
|
4
|
+
# Allows us return meaningful error messages
|
5
|
+
class AppError < StandardError
|
6
|
+
attr_reader :code
|
7
|
+
|
8
|
+
def initialize(code, msg)
|
9
|
+
super(msg)
|
10
|
+
@code = code
|
11
|
+
end
|
12
|
+
|
13
|
+
class << self
|
14
|
+
def bad_request(message)
|
15
|
+
AppError.new(503, message)
|
16
|
+
end
|
17
|
+
|
18
|
+
def message_not_found
|
19
|
+
AppError.new(404, 'message not found')
|
20
|
+
end
|
21
|
+
|
22
|
+
def http_method_not_allowed
|
23
|
+
AppError.new(405, 'method not allowed')
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Pechkin
|
2
|
+
# Http requests handler. We need fresh instance per each request. To keep
|
3
|
+
# internal state isolated
|
4
|
+
class RequestHandler
|
5
|
+
REQ_PATH_PATTERN = %r{^/(.+)/([^/]+)/?$}
|
6
|
+
|
7
|
+
attr_reader :req, :handler,
|
8
|
+
:channel_id, :message_id,
|
9
|
+
:logger
|
10
|
+
|
11
|
+
def initialize(handler, req, logger)
|
12
|
+
@handler = handler
|
13
|
+
@req = req
|
14
|
+
@logger = logger
|
15
|
+
|
16
|
+
@channel_id, @message_id = req.path_info.match(REQ_PATH_PATTERN) do |m|
|
17
|
+
[m[1], m[2]]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def handle
|
22
|
+
raise AppError.http_method_not_allowed unless post?
|
23
|
+
raise AppError.message_not_found unless message?
|
24
|
+
|
25
|
+
data = parse_data(req.body.read)
|
26
|
+
handler.handle(channel_id, message_id, data).each do |i|
|
27
|
+
logger.info "Sent #{channel_id}/#{message_id}: #{i.to_json}"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def parse_data(data)
|
34
|
+
JSON.parse(data)
|
35
|
+
rescue JSON::JSONError => e
|
36
|
+
raise AppError.bad_request(e.message)
|
37
|
+
end
|
38
|
+
|
39
|
+
def message?
|
40
|
+
return false unless @channel_id && @message_id
|
41
|
+
|
42
|
+
handler.message?(@channel_id, @message_id)
|
43
|
+
end
|
44
|
+
|
45
|
+
def post?
|
46
|
+
req.post?
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -10,16 +10,16 @@ module Pechkin
|
|
10
10
|
# Pechkin reads its configuration from provided directory structure. Basic
|
11
11
|
# layout expected to be as follows:
|
12
12
|
# .
|
13
|
-
# | - bots/
|
14
|
-
# | | - marvin.yml
|
13
|
+
# | - bots/ <- Bots configuration
|
14
|
+
# | | - marvin.yml <- Each bot described by yaml file
|
15
15
|
# | | - bender.yml
|
16
16
|
# |
|
17
|
-
# | - channels/
|
17
|
+
# | - channels/ <- Channels description
|
18
18
|
# | | - slack-repository-feed
|
19
19
|
# | | - commit-hg.yml
|
20
20
|
# | | - commit-svn.yml
|
21
21
|
# |
|
22
|
-
# | - views/
|
22
|
+
# | - views/ <- Template storage
|
23
23
|
# | - commit-hg.erb
|
24
24
|
# | - commit-svn.erb
|
25
25
|
#
|
@@ -13,7 +13,8 @@ module Pechkin # :nodoc:
|
|
13
13
|
attachments = message_desc['slack_attachments'] || {}
|
14
14
|
|
15
15
|
if text.strip.empty? && attachments.empty?
|
16
|
-
return
|
16
|
+
return { channel: channel, code: 400,
|
17
|
+
response: 'Internal error: message is empty' }
|
17
18
|
end
|
18
19
|
|
19
20
|
params = { channel: channel, text: text, attachments: attachments }
|
@@ -21,7 +22,7 @@ module Pechkin # :nodoc:
|
|
21
22
|
url = 'https://slack.com/api/chat.postMessage'
|
22
23
|
response = post_data(url, params, headers: @headers)
|
23
24
|
|
24
|
-
|
25
|
+
{ channel: channel, code: response.code.to_i, response: response.body }
|
25
26
|
end
|
26
27
|
end
|
27
28
|
end
|
@@ -12,7 +12,7 @@ module Pechkin
|
|
12
12
|
params = options.update(chat_id: chat_id, text: message)
|
13
13
|
|
14
14
|
response = post_data(method_url('sendMessage'), params)
|
15
|
-
|
15
|
+
{ chat_id: chat_id, code: response.code.to_i, response: response.body }
|
16
16
|
end
|
17
17
|
|
18
18
|
private
|
data/lib/pechkin/handler.rb
CHANGED
@@ -2,10 +2,16 @@ module Pechkin
|
|
2
2
|
# Processes feeded data chunks and sends them via connectors to needed IM
|
3
3
|
# services. Can skip some requests acording to filters.
|
4
4
|
class Handler
|
5
|
-
attr_reader :channels
|
5
|
+
attr_reader :channels, :message_matcher
|
6
|
+
attr_accessor :logger
|
6
7
|
|
7
|
-
def initialize(channels)
|
8
|
+
def initialize(channels, stdout = STDOUT, stderr = STDERR)
|
8
9
|
@channels = channels
|
10
|
+
# Create empty logger by default
|
11
|
+
@logger = Logger.new(IO::NULL)
|
12
|
+
@stdout = stdout
|
13
|
+
@stderr = stderr
|
14
|
+
@message_matcher = MessageMatcher.new
|
9
15
|
end
|
10
16
|
|
11
17
|
# Handles message request. Each request has three parameters: channel id,
|
@@ -21,20 +27,18 @@ module Pechkin
|
|
21
27
|
# deserialized json.
|
22
28
|
# @see Configuration
|
23
29
|
def handle(channel_id, msg_id, data)
|
24
|
-
channel_config =
|
25
|
-
|
26
|
-
message_config = substitute(data, fetch_message(channel_config, msg_id))
|
27
|
-
|
28
|
-
data = (message_config['variables'] || {}).merge(data)
|
29
|
-
template = message_config['template']
|
30
|
-
|
31
|
-
text = ''
|
32
|
-
text = template.render(data) unless template.nil?
|
33
|
-
|
30
|
+
channel_config, message_config, text =
|
31
|
+
prepare_message(channel_id, msg_id, data)
|
34
32
|
chats = channel_config.chat_ids
|
35
33
|
connector = channel_config.connector
|
36
34
|
|
37
|
-
|
35
|
+
if message_allowed?(message_config, data)
|
36
|
+
chats.map { |chat| connector.send_message(chat, text, message_config) }
|
37
|
+
else
|
38
|
+
logger.info "#{channel_id}/#{msg_id}: " \
|
39
|
+
"Skip sending message. Because it's not allowed"
|
40
|
+
[]
|
41
|
+
end
|
38
42
|
end
|
39
43
|
|
40
44
|
# Executes message handling and renders template using connector logic
|
@@ -47,20 +51,16 @@ module Pechkin
|
|
47
51
|
# deserialized json.
|
48
52
|
# @see Configuration
|
49
53
|
def preview(channel_id, msg_id, data)
|
50
|
-
channel_config =
|
51
|
-
|
52
|
-
message_config = substitute(data, fetch_message(channel_config, msg_id))
|
53
|
-
|
54
|
-
data = (message_config['variables'] || {}).merge(data)
|
55
|
-
template = message_config['template']
|
56
|
-
|
57
|
-
text = ''
|
58
|
-
text = template.render(data) unless template.nil?
|
59
|
-
|
54
|
+
channel_config, message_config, text =
|
55
|
+
prepare_message(channel_id, msg_id, data)
|
60
56
|
chats = channel_config.chat_ids
|
61
57
|
connector = channel_config.connector
|
62
58
|
|
63
|
-
|
59
|
+
if message_allowed?(message_config, data)
|
60
|
+
connector.preview(chats, text, message_config)
|
61
|
+
else
|
62
|
+
puts "No message sent beacuse it's not allowed"
|
63
|
+
end
|
64
64
|
end
|
65
65
|
|
66
66
|
def message?(channel_id, msg_id)
|
@@ -69,6 +69,10 @@ module Pechkin
|
|
69
69
|
|
70
70
|
private
|
71
71
|
|
72
|
+
def puts(msg)
|
73
|
+
@stdout.puts(msg)
|
74
|
+
end
|
75
|
+
|
72
76
|
# Find channel by it's id or trow ChannelNotFoundError
|
73
77
|
def fetch_channel(channel_id)
|
74
78
|
raise ChannelNotFoundError, channel_id unless channels.key?(channel_id)
|
@@ -84,6 +88,24 @@ module Pechkin
|
|
84
88
|
message_list[msg_id]
|
85
89
|
end
|
86
90
|
|
91
|
+
def message_allowed?(message_config, data)
|
92
|
+
message_matcher.matches?(message_config, data)
|
93
|
+
end
|
94
|
+
|
95
|
+
def prepare_message(channel_id, msg_id, data)
|
96
|
+
channel_config = fetch_channel(channel_id)
|
97
|
+
# Find message and try substitute values to message parameters.
|
98
|
+
message_config = substitute(data, fetch_message(channel_config, msg_id))
|
99
|
+
|
100
|
+
data = (message_config['variables'] || {}).merge(data)
|
101
|
+
template = message_config['template']
|
102
|
+
|
103
|
+
text = ''
|
104
|
+
text = template.render(data) unless template.nil?
|
105
|
+
|
106
|
+
[channel_config, message_config, text]
|
107
|
+
end
|
108
|
+
|
87
109
|
def substitute(data, message_desc)
|
88
110
|
substitute_recursive(Substitute.new(data), message_desc)
|
89
111
|
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module Pechkin
|
2
|
+
class MessageMatchError < StandardError; end
|
3
|
+
# Allows to match message configuration against received data.
|
4
|
+
#
|
5
|
+
# Data is checked againts either allow or forbid rules. But not both at the
|
6
|
+
# same time. Each field can contain list of rules to check. 'allow' list means
|
7
|
+
# we need at least one matching rule to allow data processing. And 'forbid'
|
8
|
+
# list respectievly means we need at least one matching rule to decline data
|
9
|
+
# processing.
|
10
|
+
class MessageMatcher
|
11
|
+
KEY_ALLOW = 'allow'.freeze
|
12
|
+
KEY_FORBID = 'forbid'.freeze
|
13
|
+
# Checks data object against allow / forbid rule sets in message
|
14
|
+
# configuration. If data object matches rules it means we can process this
|
15
|
+
# data and send message.
|
16
|
+
#
|
17
|
+
# @param message_config [Hash] message description.
|
18
|
+
# @param data [Hash] request object that need to be inspected whether we
|
19
|
+
# should process this data or not
|
20
|
+
# @return [Boolean] is data object matches message_config rules or not
|
21
|
+
def matches?(message_config, data)
|
22
|
+
check(message_config)
|
23
|
+
|
24
|
+
if message_config.key?(KEY_ALLOW)
|
25
|
+
rules = message_config[KEY_ALLOW]
|
26
|
+
rules.any? { |r| check_rule(r, data) }
|
27
|
+
elsif message_config.key?(KEY_FORBID)
|
28
|
+
rules = message_config[KEY_FORBID]
|
29
|
+
rules.all? { |r| !check_rule(r, data) }
|
30
|
+
else
|
31
|
+
true
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def check(msg)
|
38
|
+
return unless msg.key?(KEY_ALLOW) && msg.key?(KEY_FORBID)
|
39
|
+
|
40
|
+
raise MessageMatchError, 'Both allow and forbid present in message config'
|
41
|
+
end
|
42
|
+
|
43
|
+
# Check rule object against data. Rules are checked recursievly, i.e. we
|
44
|
+
# can take one field in rule and check it separately as new rule. If all
|
45
|
+
# fields are passed check then whole rule passed.
|
46
|
+
def check_rule(rule, data)
|
47
|
+
if rule.is_a?(Hash)
|
48
|
+
check_hash_rule(rule, data)
|
49
|
+
elsif rule.is_a?(Array)
|
50
|
+
check_array_rule(rule, data)
|
51
|
+
elsif rule.is_a?(String)
|
52
|
+
check_string_rule(rule, data)
|
53
|
+
else
|
54
|
+
rule.eql?(data)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def check_hash_rule(hash, data)
|
59
|
+
return false unless data.is_a?(Hash)
|
60
|
+
|
61
|
+
hash.all? do |key, value|
|
62
|
+
data.key?(key) && check_rule(value, data[key])
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def check_array_rule(array, data)
|
67
|
+
return false unless data.is_a?(Array)
|
68
|
+
|
69
|
+
# Deep array check needs to be done against all elements so we zip arrays
|
70
|
+
# to pair each rule with data element
|
71
|
+
array.zip(data).all? { |r, d| check_rule(r, d) }
|
72
|
+
end
|
73
|
+
|
74
|
+
def check_string_rule(str, data)
|
75
|
+
str.eql? data
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
data/lib/pechkin/version.rb
CHANGED
metadata
CHANGED
@@ -1,29 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: pechkin
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ilya Arkhanhelsky
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-02-
|
11
|
+
date: 2020-02-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
-
- !ruby/object:Gem::Dependency
|
14
|
-
name: grape
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
16
|
-
requirements:
|
17
|
-
- - '='
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: 1.1.0
|
20
|
-
type: :runtime
|
21
|
-
prerelease: false
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
23
|
-
requirements:
|
24
|
-
- - '='
|
25
|
-
- !ruby/object:Gem::Version
|
26
|
-
version: 1.1.0
|
27
13
|
- !ruby/object:Gem::Dependency
|
28
14
|
name: htauth
|
29
15
|
requirement: !ruby/object:Gem::Requirement
|
@@ -90,6 +76,10 @@ files:
|
|
90
76
|
- bin/pechkin
|
91
77
|
- lib/pechkin.rb
|
92
78
|
- lib/pechkin/app.rb
|
79
|
+
- lib/pechkin/app/app.rb
|
80
|
+
- lib/pechkin/app/app_builder.rb
|
81
|
+
- lib/pechkin/app/app_error.rb
|
82
|
+
- lib/pechkin/app/request_handler.rb
|
93
83
|
- lib/pechkin/auth.rb
|
94
84
|
- lib/pechkin/channel.rb
|
95
85
|
- lib/pechkin/cli.rb
|
@@ -111,6 +101,7 @@ files:
|
|
111
101
|
- lib/pechkin/connector_telegram.rb
|
112
102
|
- lib/pechkin/exceptions.rb
|
113
103
|
- lib/pechkin/handler.rb
|
104
|
+
- lib/pechkin/message_matcher.rb
|
114
105
|
- lib/pechkin/message_template.rb
|
115
106
|
- lib/pechkin/prometheus_utils.rb
|
116
107
|
- lib/pechkin/substitute.rb
|