active_endpoint 0.2.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.
Files changed (67) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +55 -0
  3. data/.rspec +2 -0
  4. data/.rubocop.yml +60 -0
  5. data/.travis.yml +5 -0
  6. data/CODE_OF_CONDUCT.md +74 -0
  7. data/Gemfile +25 -0
  8. data/Gemfile.lock +140 -0
  9. data/LICENSE +21 -0
  10. data/README.md +233 -0
  11. data/Rakefile +6 -0
  12. data/active_endpoint.gemspec +28 -0
  13. data/app/assets/config/active_endpoint_manifest.js +2 -0
  14. data/app/assets/javascripts/active_endpoint/application.js +2 -0
  15. data/app/assets/stylesheets/active_endpoint/application.css +10 -0
  16. data/app/controllers/active_endpoint/application_controller.rb +5 -0
  17. data/app/controllers/active_endpoint/dashboard_controller.rb +7 -0
  18. data/app/controllers/active_endpoint/probes_controller.rb +22 -0
  19. data/app/controllers/active_endpoint/unregistred_probes_controller.rb +14 -0
  20. data/app/helpers/active_endpoint/application_helper.rb +105 -0
  21. data/app/models/active_endpoint/application_record.rb +5 -0
  22. data/app/models/active_endpoint/probe.rb +52 -0
  23. data/app/models/active_endpoint/unregistred_probe.rb +3 -0
  24. data/app/views/active_endpoint/application/_flashes.html.erb +5 -0
  25. data/app/views/active_endpoint/application/_header.html.erb +18 -0
  26. data/app/views/active_endpoint/dashboard/_blacklist.html.erb +57 -0
  27. data/app/views/active_endpoint/dashboard/_constraints.html.erb +117 -0
  28. data/app/views/active_endpoint/dashboard/_settings.html.erb +13 -0
  29. data/app/views/active_endpoint/dashboard/_tags.html.erb +20 -0
  30. data/app/views/active_endpoint/dashboard/index.html.erb +8 -0
  31. data/app/views/active_endpoint/probes/index.html.erb +28 -0
  32. data/app/views/active_endpoint/probes/show.html.erb +43 -0
  33. data/app/views/active_endpoint/probes/show_response.html.erb +9 -0
  34. data/app/views/active_endpoint/unregistred_probes/index.html.erb +28 -0
  35. data/app/views/layouts/active_endpoint/application.html.erb +39 -0
  36. data/bin/console +14 -0
  37. data/bin/rails +13 -0
  38. data/bin/setup +8 -0
  39. data/config/routes.rb +8 -0
  40. data/lib/active_endpoint.rb +60 -0
  41. data/lib/active_endpoint/concerns/configurable.rb +29 -0
  42. data/lib/active_endpoint/concerns/constraintable.rb +51 -0
  43. data/lib/active_endpoint/concerns/optionable.rb +41 -0
  44. data/lib/active_endpoint/concerns/rails_routable.rb +49 -0
  45. data/lib/active_endpoint/engine.rb +15 -0
  46. data/lib/active_endpoint/extentions/active_record.rb +30 -0
  47. data/lib/active_endpoint/logger.rb +28 -0
  48. data/lib/active_endpoint/proxy.rb +64 -0
  49. data/lib/active_endpoint/rails/middleware.rb +17 -0
  50. data/lib/active_endpoint/rails/railtie.rb +13 -0
  51. data/lib/active_endpoint/request.rb +79 -0
  52. data/lib/active_endpoint/response.rb +17 -0
  53. data/lib/active_endpoint/routes/blacklist.rb +67 -0
  54. data/lib/active_endpoint/routes/cache/proxy.rb +22 -0
  55. data/lib/active_endpoint/routes/cache/proxy/redis_store_proxy.rb +41 -0
  56. data/lib/active_endpoint/routes/cache/store.rb +73 -0
  57. data/lib/active_endpoint/routes/constraint_rule.rb +38 -0
  58. data/lib/active_endpoint/routes/constraints.rb +81 -0
  59. data/lib/active_endpoint/routes/matcher.rb +51 -0
  60. data/lib/active_endpoint/routes/momento.rb +81 -0
  61. data/lib/active_endpoint/storage.rb +112 -0
  62. data/lib/active_endpoint/tags.rb +15 -0
  63. data/lib/active_endpoint/version.rb +3 -0
  64. data/lib/generators/active_endpoint/install_generator.rb +35 -0
  65. data/lib/generators/templates/active_endpoint.rb +40 -0
  66. data/lib/generators/templates/migration.erb +30 -0
  67. metadata +109 -0
@@ -0,0 +1,15 @@
1
+ module ActiveEndpoint
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace ActiveEndpoint
4
+
5
+ initializer 'active_endpoint.assets.precompile' do |app|
6
+ app.config.assets.precompile += %w()
7
+ end
8
+
9
+ config.generators do |g|
10
+ g.orm :active_record
11
+ g.template_engine :erb
12
+ g.test_framework :rspec
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,30 @@
1
+ module ActiveEndpoint
2
+ module Extentions
3
+ module ActiveRecord
4
+ METHODS = {
5
+ greater_than: '>',
6
+ greater_than_or_equal_to: '>=',
7
+ equal_to: '=',
8
+ less_than: '<',
9
+ less_than_or_equal_to: '<='
10
+ }.freeze
11
+
12
+ def tagged_as(tag, tags = ActiveEndpoint.tags.definition)
13
+ return [] unless tags.keys.include?(tag)
14
+
15
+ time_operators = tags[tag]
16
+ last_operator_index = time_operators.keys.length - 1
17
+
18
+ query = ''
19
+ time_operators.each_with_index do |(key, value), index|
20
+ operator = METHODS[key]
21
+ duration = (value.to_f / 1000).to_s
22
+ and_operator = last_operator_index == index ? '' : ' AND '
23
+ query << 'duration ' + operator + ' ' + duration + ' ' + and_operator
24
+ end
25
+
26
+ where(query)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,28 @@
1
+ module ActiveEndpoint
2
+ class Logger
3
+ class << self
4
+ def info(caller, info, force = nil)
5
+ return if force.nil? || !force
6
+ logger.info(message(caller, info))
7
+ end
8
+
9
+ def debug(caller, message)
10
+ logger.debug(message(caller, message))
11
+ end
12
+
13
+ def error(caller, error)
14
+ logger.error(message(caller, error))
15
+ end
16
+
17
+ private
18
+
19
+ def logger
20
+ ::Rails.logger
21
+ end
22
+
23
+ def message(caller, message)
24
+ "ActiveEndpoint::Logger [#{caller}] - #{message}"
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,64 @@
1
+ module ActiveEndpoint
2
+ class Proxy
3
+ def initialize
4
+ @created_at = Time.now
5
+ @matcher = ActiveEndpoint::Routes::Matcher.new
6
+ @logger = ActiveEndpoint.logger
7
+ @notifier = ActiveSupport::Notifications
8
+ end
9
+
10
+ def track(env, &block)
11
+ request = ActiveEndpoint::Request.new(env)
12
+
13
+ if ActiveEndpoint.log_debug_info
14
+ @logger.debug('ActiveEndpoint::Blacklist', ActiveEndpoint.blacklist.inspect)
15
+ @logger.debug('ActiveEndpoint::Constraints', ActiveEndpoint.constraints.inspect)
16
+ end
17
+
18
+ if @matcher.whitelisted?(request)
19
+ track_begin(request)
20
+ status, headers, response = yield block
21
+ track_end(response)
22
+ [status, headers, response]
23
+ else
24
+ register(request) if @matcher.unregistred?(request)
25
+
26
+ yield block
27
+ end
28
+ rescue => error
29
+ @logger.error(self.class, error)
30
+
31
+ yield block
32
+ end
33
+
34
+ private
35
+
36
+ def track_begin(request)
37
+ @request = request.probe
38
+ end
39
+
40
+ def track_end(response, finished_at = Time.now)
41
+ @response = ActiveEndpoint::Response.new(response).probe
42
+ @finished_at = finished_at
43
+
44
+ probe = {
45
+ created_at: @created_at,
46
+ finished_at: @finished_at,
47
+ request: @request,
48
+ response: @response
49
+ }
50
+
51
+ @notifier.instrument('active_endpoint.tracked_probe', probe: probe) if @matcher.allow_account?(@request)
52
+ end
53
+
54
+ def register(request)
55
+ unregistred = {
56
+ created_at: @created_at,
57
+ finished_at: @finished_at,
58
+ request: request.probe
59
+ }
60
+
61
+ @notifier.instrument('active_endpoint.unregistred_probe', probe: unregistred) if @matcher.allow_register?(request)
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,17 @@
1
+ module ActiveEndpoint
2
+ module Rails
3
+ class Middleware
4
+ def initialize(app)
5
+ @app = app
6
+ end
7
+
8
+ def call(env)
9
+ dup._call(env, ActiveEndpoint::Proxy.new)
10
+ end
11
+
12
+ def _call(env, proxy)
13
+ proxy.track(env) { @app.call(env) }
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,13 @@
1
+ module ActiveEndpoint
2
+ module Rails
3
+ class Railtie < ::Rails::Railtie
4
+ initializer 'active_endpoint.configure_rails_initialization' do |app|
5
+ ActiveSupport.on_load(:active_record) do
6
+ ActiveRecord::Base.send(:extend, ::ActiveEndpoint::Extentions::ActiveRecord)
7
+ end
8
+
9
+ app.middleware.insert(0, ActiveEndpoint::Rails::Middleware)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,79 @@
1
+ module ActiveEndpoint
2
+ class Request < Rack::Request
3
+ include RailsRoutable
4
+
5
+ def probe
6
+ {
7
+ base_url: base_url,
8
+ body: body,
9
+ content_charset: content_charset,
10
+ content_length: content_length,
11
+ content_type: content_type,
12
+ endpoint: endpoint,
13
+ fullpath: fullpath,
14
+ http_version: http_version,
15
+ http_connection: http_connection,
16
+ http_accept_encoding: http_accept_encoding,
17
+ http_accept_language: http_accept_language,
18
+ ip: ip,
19
+ media_type: media_type,
20
+ media_type_params: media_type_params,
21
+ method: method,
22
+ params: params,
23
+ path: path,
24
+ path_info: path_info,
25
+ pattern: pattern,
26
+ port: port,
27
+ protocol: protocol,
28
+ query_string: query_string,
29
+ request_method: request_method,
30
+ server_name: server_name,
31
+ ssl: ssl?,
32
+ url: url,
33
+ xhr: xhr?
34
+ }
35
+ end
36
+
37
+ def method
38
+ request_method.downcase.to_sym
39
+ end
40
+
41
+ def endpoint
42
+ rails_endpoint_name(rails_endpoint(self))
43
+ end
44
+
45
+ private
46
+
47
+ def http_accept_encoding
48
+ get_header('HTTP_ACCEPT_ENCODING')
49
+ end
50
+
51
+ def http_accept_language
52
+ get_header('HTTP_ACCEPT_LANGUAGE')
53
+ end
54
+
55
+ def http_connection
56
+ get_header('HTTP_CONNECTION')
57
+ end
58
+
59
+ def http_version
60
+ get_header('HTTP_VERSION')
61
+ end
62
+
63
+ def params
64
+ rails_request_params(self)
65
+ end
66
+
67
+ def pattern
68
+ rails_route_pattern(self)
69
+ end
70
+
71
+ def protocol
72
+ get_header('SERVER_PROTOCOL')
73
+ end
74
+
75
+ def server_name
76
+ get_header('SERVER_NAME')
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,17 @@
1
+ module ActiveEndpoint
2
+ class Response
3
+ def initialize(response)
4
+ @response = response
5
+ end
6
+
7
+ def probe
8
+ {
9
+ body: body
10
+ }
11
+ end
12
+
13
+ def body
14
+ @response.join if @response.respond_to? :join
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,67 @@
1
+ module ActiveEndpoint
2
+ module Routes
3
+ class Blacklist < Momento
4
+ def initialize
5
+ super(Array)
6
+ end
7
+
8
+ private
9
+
10
+ def add_endpoint(options)
11
+ @endpoints << fetch_endpoint(options)
12
+ end
13
+
14
+ def add_resources(options)
15
+ resources = fetch_resources(options)
16
+ actions = fetch_actions(options)
17
+ scope = fetch_scope(options)
18
+
19
+ if actions.present? && actions.any?
20
+ temp_actions = []
21
+ if resources.is_a?(Array)
22
+ resources.each do |controller_name|
23
+ actions.each { |action| temp_actions << "#{controller_name}##{action}" }
24
+ end
25
+ else
26
+ actions.each { |action| temp_actions << "#{resources}##{action}" }
27
+ end
28
+ @actions += apply(scope, temp_actions)
29
+ else
30
+ temp_resources = []
31
+ if resources.is_a?(Array)
32
+ resources.each { |resource| temp_resources << resource }
33
+ else
34
+ temp_resources << resources
35
+ end
36
+ @resources += apply(scope, temp_resources)
37
+ end
38
+ end
39
+
40
+ def add_scopes(options)
41
+ scope = fetch_scope(options)
42
+ @scopes << scope unless @scopes.include?(scope)
43
+ end
44
+
45
+ def present_endpoint?(request)
46
+ @endpoints.include?(request[:endpoint])
47
+ end
48
+
49
+ def present_resource?(request)
50
+ reduce_state(@resources, request)
51
+ end
52
+
53
+ def present_action?(request)
54
+ @actions.include?(request[:endpoint])
55
+ end
56
+
57
+ def present_scope?(request)
58
+ reduce_state(@scopes, request)
59
+ end
60
+
61
+ def apply(scope, collection)
62
+ return collection unless scope.present?
63
+ collection.map { |subject| "#{scope}/#{subject}" }
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,22 @@
1
+ module ActiveEndpoint
2
+ module Routes
3
+ module Cache
4
+ module Proxy
5
+ class AdapterError < ::StandardError; end
6
+
7
+ CLIENTS = {
8
+ redis: 'RedisStoreProxy'
9
+ }.freeze
10
+
11
+ def self.build(adapter)
12
+ unless CLIENTS.keys.include?(adapter)
13
+ message "You try to use unsupported cache store adapter! #{adapter}\n"
14
+ raise ActiveEndpoint::Routes::Cache::Proxy::AdapterError.new(message)
15
+ end
16
+
17
+ "ActiveEndpoint::Routes::Cache::Proxy::#{CLIENTS[adapter]}".constantize.new
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,41 @@
1
+ module ActiveEndpoint
2
+ module Routes
3
+ module Cache
4
+ module Proxy
5
+ class RedisStoreProxy
6
+ def initialize
7
+ @prefix = ActiveEndpoint.cache_prefix
8
+ @store = ::Redis::Store.new
9
+ end
10
+
11
+ def read(unprefixed_key)
12
+ @store.get("#{@prefix}:#{unprefixed_key}")
13
+ end
14
+
15
+ def write(unprefixed_key, value, expires_in = nil)
16
+ store_key = store_key(unprefixed_key)
17
+
18
+ if expires_in.present?
19
+ @store.setex(store_key, expires_in, value)
20
+ else
21
+ @store.set(store_key, value)
22
+ end
23
+
24
+ true
25
+ end
26
+
27
+ def expires_in(unprefixed_key)
28
+ time = @store.ttl(store_key(unprefixed_key)).to_i
29
+ time == -1 || time == -2 ? 0 : time
30
+ end
31
+
32
+ private
33
+
34
+ def store_key(unprefixed_key)
35
+ "#{@prefix}:#{unprefixed_key}"
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,73 @@
1
+ module ActiveEndpoint
2
+ module Routes
3
+ module Cache
4
+ class Store
5
+ delegate :read, to: :@store
6
+ delegate :write, to: :@store
7
+ delegate :expires_in, to: :@store
8
+
9
+ def initialize
10
+ @store = ActiveEndpoint::Routes::Cache::Proxy.build(ActiveEndpoint.cache_store_client)
11
+ @logger = ActiveEndpoint.logger
12
+ @notifier = ActiveSupport::Notifications
13
+ end
14
+
15
+ def allow?(rule)
16
+ rule_cache_allow?(rule) && storage_cache_allow?(rule)
17
+ end
18
+
19
+ def unregistred?(probe)
20
+ path = probe[:path]
21
+ read(path).present? ? false : write(path, :unregistred)
22
+ end
23
+
24
+ private
25
+
26
+ def rule_cache_allow?(options)
27
+ cache_allow?(:rule, options)
28
+ end
29
+
30
+ def storage_cache_allow?(options)
31
+ cache_allow?(:storage, options) do |key, period|
32
+ expired = {
33
+ key: key,
34
+ period: period
35
+ }
36
+
37
+ @notifier.instrument('active_endpoint.clean_expired', expired: expired)
38
+ end
39
+ end
40
+
41
+ def cache_allow?(prefix, options, &block)
42
+
43
+ key = "#{prefix}:#{options[:key]}"
44
+ constraints = options[prefix]
45
+
46
+ cache = read(key)
47
+ limit = cache.nil? ? cache : cache.to_i
48
+ period = expires_in(key)
49
+
50
+ if ActiveEndpoint.log_debug_info
51
+ @logger.debug('ActiveEndpoint::Cache::Store Prefix', prefix)
52
+ @logger.debug('ActiveEndpoint::Cache::Store Limit', limit)
53
+ @logger.debug('ActiveEndpoint::Cache::Store Period', period)
54
+ end
55
+
56
+ limited = limit.present? && limit.zero?
57
+ expired = period.zero?
58
+
59
+ if limit.nil? && expired && block_given?
60
+ yield(options[:key], constraints[:period])
61
+ end
62
+
63
+ if limit.present? && !expired
64
+ return false if limited
65
+ write(key, limit - 1, period)
66
+ else
67
+ write(key, constraints[:limit] - 1, constraints[:period])
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end