pechkin 1.0.3 → 1.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b1676e5e77d5898b67a9614012f5d982cb2c75c97847ba8fe22bfaf409988f78
4
- data.tar.gz: bd7a612a913ce4433ec8a4d78687f5eddb75b1cf28ca6675f33833b0ebc76955
3
+ metadata.gz: 59912e94ce5225522475cb9f77b3bbb83c398a5dada6b1f937c02c0a886e2e8f
4
+ data.tar.gz: dcf67693f3ecaa7de96ef00ee3e4bfc0347d6ee45f546a3c8e7ae9e5d39b34b0
5
5
  SHA512:
6
- metadata.gz: 806a857ddaf52ead16bbeec58bd1b9b4d3af8690011297fa35014058d3f0b1510ffa6694e4a651f33eab71814cae677b12d169df2c1a2fbd26eb57fd0f017169
7
- data.tar.gz: c699ddf5ad93cc7e5c9996fa398d734a5029fef470311c488cfc0d3df4d7124e8bb7b02498e7e69e27ce64d8109f5bab27f264642feefba5dc5a7ab6947877d1
6
+ metadata.gz: d071d69449005cf0dfae928a148391ea1247876bcc06cd52c588ca84d5210e37a66a0838e10511ed0a6554b1ecf6904961467e6d1fef916676a4296f34687367
7
+ data.tar.gz: d59c5fcbf1c97be064075bf012ca0d760d65edd911bd4cd551efcc64468292ed213423fcfb893823aa57191b38433bb803a09158cd3da2da18558f8a7bd5d795
@@ -3,6 +3,8 @@ require 'rack'
3
3
  require 'logger'
4
4
  require 'prometheus/middleware/collector'
5
5
  require 'prometheus/middleware/exporter'
6
+ require 'htauth'
7
+ require 'base64'
6
8
 
7
9
  require_relative 'pechkin/cli'
8
10
  require_relative 'pechkin/exceptions'
@@ -14,6 +16,8 @@ require_relative 'pechkin/connector_telegram'
14
16
  require_relative 'pechkin/channel'
15
17
  require_relative 'pechkin/configuration'
16
18
  require_relative 'pechkin/substitute'
19
+ require_relative 'pechkin/prometheus_utils'
20
+ require_relative 'pechkin/auth'
17
21
  require_relative 'pechkin/app'
18
22
 
19
23
  module Pechkin # :nodoc:
@@ -33,20 +37,24 @@ module Pechkin # :nodoc:
33
37
 
34
38
  def initialize(options)
35
39
  @options = options
36
- @configuration = Configuration.load_from_directory(options.config_file)
37
- @handler = Handler.new(@configuration.channels)
38
40
  end
39
41
 
40
42
  def run
43
+ if options.add_auth
44
+ add_auth
45
+ exit 0
46
+ end
47
+
48
+ @configuration = Configuration.load_from_directory(options.config_file)
49
+ @handler = Handler.new(@configuration.channels)
41
50
  configuration.list if options.list?
42
- exit 0 if options.check?
51
+ return if options.check?
43
52
 
44
53
  if options.send_data
45
54
  send_data
46
- exit 0
55
+ else
56
+ run_server
47
57
  end
48
-
49
- run_server
50
58
  end
51
59
 
52
60
  def run_server
@@ -79,5 +87,11 @@ module Pechkin # :nodoc:
79
87
  [m[1], m[2]]
80
88
  end
81
89
  end
90
+
91
+ def add_auth
92
+ user, password = options.add_auth.split(':')
93
+ Pechkin::Auth::Manager.new(options.htpasswd).add(user, password)
94
+ puts IO.read(options.htpasswd)
95
+ end
82
96
  end
83
97
  end
@@ -5,14 +5,19 @@ module Pechkin
5
5
  def build(handler, options)
6
6
  app = App.new
7
7
  app.handler = handler
8
-
8
+ prometheus = Pechkin::PrometheusUtils.registry
9
9
  logger = create_logger(options.log_dir)
10
10
 
11
11
  Rack::Builder.app do
12
12
  use Rack::CommonLogger, logger
13
13
  use Rack::Deflater
14
- use Prometheus::Middleware::Collector
15
- use Prometheus::Middleware::Exporter
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
16
21
 
17
22
  run app
18
23
  end
@@ -0,0 +1,57 @@
1
+ module Pechkin
2
+ module Auth
3
+ # Utility class for altering htpasswd files
4
+ class Manager
5
+ attr_reader :htpasswd
6
+ def initialize(htpasswd)
7
+ @htpasswd = htpasswd
8
+ end
9
+
10
+ def add(user, password)
11
+ m = File.exist?(htpasswd) ? HTAuth::File::ALTER : HTAuth::File::CREATE
12
+ HTAuth::PasswdFile.open(htpasswd, m) do |f|
13
+ f.add_or_update(user, password, 'md5')
14
+ end
15
+ end
16
+ end
17
+
18
+ # Auth middleware to check if provided auth can be found in .htpasswd file
19
+ class Middleware
20
+ attr_reader :htpasswd
21
+
22
+ def initialize(app, auth_file:)
23
+ @htpasswd = HTAuth::PasswdFile.open(auth_file) if File.exist?(auth_file)
24
+ @app = app
25
+ end
26
+
27
+ def call(env)
28
+ if authorized?(env)
29
+ @app.call(env)
30
+ else
31
+ body = { status: 'error', reason: 'unathorized' }.to_json
32
+ ['401', { 'Content-Type' => 'application/json' }, [body]]
33
+ end
34
+ rescue StandardError => e
35
+ puts e.backtrace.reverse.join('\n\t')
36
+ body = { status: 'error', reason: e.message }.to_json
37
+ ['503', { 'Content-Type' => 'application/json' }, [body]]
38
+ end
39
+
40
+ private
41
+
42
+ def authorized?(env)
43
+ return true unless htpasswd
44
+
45
+ auth = env['HTTP_AUTHORIZATION'] || ''
46
+ auth.match(/^Basic (.+)$/) do |m|
47
+ check_auth(*Base64.decode64(m[1]).split(':'))
48
+ end
49
+ end
50
+
51
+ def check_auth(user, password)
52
+ e = htpasswd.fetch(user)
53
+ e && e.authenticated?(password)
54
+ end
55
+ end
56
+ end
57
+ end
@@ -43,7 +43,7 @@ module Pechkin
43
43
  exit 2
44
44
  else
45
45
  parser.parse(args)
46
- values
46
+ new.post_init(values)
47
47
  end
48
48
  end
49
49
 
@@ -89,6 +89,9 @@ module Pechkin
89
89
 
90
90
  # Command Line Parser Builder
91
91
  class CLI
92
+ # Default file name for htpasswd file with auth credentials
93
+ PECHKIN_HTPASSWD_FILE = 'pechkin.htpasswd'.freeze
94
+
92
95
  extend CLIHelper
93
96
 
94
97
  separator 'Run options'
@@ -104,28 +107,49 @@ module Pechkin
104
107
 
105
108
  opt :log_dir, names: ['--log-dir [DIR]'],
106
109
  desc: 'Path to log directory. Output will be writen to' \
107
- 'pechkin.log file. If not specified will write to' \
110
+ 'pechkin.log file. If not specified will write to ' \
108
111
  'STDOUT'
112
+ opt :htpasswd, names: ['--auth-file FILE'],
113
+ desc: 'Path to .htpasswd file. By default ' \
114
+ '`pechkin.htpasswd` file will be looked up in ' \
115
+ 'configuration directory and if found then ' \
116
+ 'authorization will be enabled implicitly. ' \
117
+ 'Providing this option enables htpasswd based ' \
118
+ 'authorization explicitly. When making requests use ' \
119
+ 'Basic auth to authorize.'
109
120
 
110
121
  separator 'Utils for configuration maintenance'
111
-
112
122
  opt :list?, names: ['-l', '--[no-]list'],
113
123
  desc: 'List all endpoints'
114
124
 
115
125
  opt :check?, names: ['-k', '--[no-]check'],
116
126
  desc: 'Load configuration and exit'
117
127
  opt :send_data, names: ['-s', '--send ENDPOINT'],
118
- desc: 'Send data to specified ENDPOINT and exit. Requires' \
119
- '--data to be set.'
128
+ desc: 'Send data to specified ENDPOINT and exit. ' \
129
+ 'Requires --data to be set.'
120
130
  opt :preview, names: ['--preview'],
121
131
  desc: 'Print rendering result to STDOUT and exit. ' \
122
- 'Use with send'
132
+ 'Use with --send'
123
133
  opt :data, names: ['--data DATA'],
124
134
  desc: 'Data to send with --send flag. Json string or @filename.'
125
135
 
126
- separator 'Debug options'
136
+ separator 'Auth utils'
137
+ opt :add_auth, names: ['--add-auth USER:PASSWORD'],
138
+ desc: 'Add auth entry to .htpasswd file. By default ' \
139
+ 'pechkin.htpasswd from configuration directory ' \
140
+ 'will be used. Use --auth-file to specify other ' \
141
+ 'file to update. If file does not exist it will be ' \
142
+ 'created.'
127
143
 
144
+ separator 'Debug options'
128
145
  opt :debug?, names: ['--[no-]debug'],
129
146
  desc: 'Print debug information and stack trace on errors'
147
+
148
+ def post_init(values)
149
+ default_htpasswd = File.join(values.config_file, PECHKIN_HTPASSWD_FILE)
150
+ values.htpasswd ||= default_htpasswd
151
+
152
+ values
153
+ end
130
154
  end
131
155
  end
@@ -8,31 +8,14 @@ module Pechkin
8
8
 
9
9
  # Message template to render final message.
10
10
  class MessageTemplate
11
- class << self
12
- def ruby_v260_or_later?(ruby_version = RUBY_VERSION)
13
- # Need to compare two versions, one is 2.6.0 - which supports keyword
14
- # arguments in ERB#initialize. Everything below that should use legacy
15
- # arguments
16
- rb_v260 = [2, 6, 0]
17
- rb_current = ruby_version.split('.').map(&:to_i)
18
-
19
- rb_v260.zip(rb_current).each do |x, y|
20
- x ||= 0
21
- y ||= 0
22
- return false if y < x
23
- end
24
-
25
- true
26
- end
27
- end
28
-
29
- RUBY_V260 = MessageTemplate.ruby_v260_or_later?
11
+ ERB_INITIALIZE_KEYWORD_ARGUMENTS = ERB.instance_method(:initialize)
12
+ .parameters.assoc(:key)
30
13
 
31
14
  def initialize(erb)
32
15
  # ERB#initialize has different signature starting from Ruby 2.6.*
33
16
  # See link:
34
17
  # https://github.com/ruby/ruby/blob/2311087/NEWS#stdlib-updates-outstanding-ones-only
35
- if MessageTemplate::RUBY_V260
18
+ if MessageTemplate::ERB_INITIALIZE_KEYWORD_ARGUMENTS # Ruby 2.6+
36
19
  @erb_template = ERB.new(erb, trim_mode: '-')
37
20
  else
38
21
  safe_level = nil
@@ -0,0 +1,12 @@
1
+ module Pechkin
2
+ module PrometheusUtils # :nodoc:
3
+ class << self
4
+ def registry
5
+ registry = ::Prometheus::Client.registry
6
+ registry.gauge(:pechkin_start_time_seconds,
7
+ docstring: 'Startup timestamp').set(Time.now.to_i)
8
+ registry
9
+ end
10
+ end
11
+ end
12
+ end
@@ -1,7 +1,7 @@
1
1
  module Pechkin
2
2
  # Keeps actual version
3
3
  module Version
4
- VERSION = [1, 0, 3].freeze
4
+ VERSION = [1, 1, 0].freeze
5
5
  class << self
6
6
  def version_string
7
7
  VERSION.join('.')
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.0.3
4
+ version: 1.1.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: 2019-12-11 00:00:00.000000000 Z
11
+ date: 2019-12-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: grape
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - '='
25
25
  - !ruby/object:Gem::Version
26
26
  version: 1.1.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: htauth
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '='
32
+ - !ruby/object:Gem::Version
33
+ version: 2.0.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '='
39
+ - !ruby/object:Gem::Version
40
+ version: 2.0.0
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: prometheus-client
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -44,14 +58,14 @@ dependencies:
44
58
  requirements:
45
59
  - - '='
46
60
  - !ruby/object:Gem::Version
47
- version: 2.0.6
61
+ version: 2.0.8
48
62
  type: :runtime
49
63
  prerelease: false
50
64
  version_requirements: !ruby/object:Gem::Requirement
51
65
  requirements:
52
66
  - - '='
53
67
  - !ruby/object:Gem::Version
54
- version: 2.0.6
68
+ version: 2.0.8
55
69
  description:
56
70
  email: ilya.arkhanhelsky at gmail.com
57
71
  executables:
@@ -62,6 +76,7 @@ files:
62
76
  - bin/pechkin
63
77
  - lib/pechkin.rb
64
78
  - lib/pechkin/app.rb
79
+ - lib/pechkin/auth.rb
65
80
  - lib/pechkin/channel.rb
66
81
  - lib/pechkin/cli.rb
67
82
  - lib/pechkin/configuration.rb
@@ -76,6 +91,7 @@ files:
76
91
  - lib/pechkin/exceptions.rb
77
92
  - lib/pechkin/handler.rb
78
93
  - lib/pechkin/message_template.rb
94
+ - lib/pechkin/prometheus_utils.rb
79
95
  - lib/pechkin/substitute.rb
80
96
  - lib/pechkin/version.rb
81
97
  homepage: https://github.com/iarkhanhelsky/pechkin