insights-api-common 4.0.0 → 4.1.1
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/README.md +2 -2
- data/lib/insights/api/common.rb +1 -0
- data/lib/insights/api/common/application_controller_mixins/exception_handling.rb +51 -7
- data/lib/insights/api/common/custom_exceptions.rb +16 -0
- data/lib/insights/api/common/metrics.rb +27 -3
- data/lib/insights/api/common/open_api.rb +1 -0
- data/lib/insights/api/common/open_api/generator.rb +22 -3
- data/lib/insights/api/common/open_api/serializer.rb +23 -10
- data/lib/insights/api/common/open_api/version_from_prefix.rb +14 -0
- data/lib/insights/api/common/rbac/seed.rb +1 -1
- data/lib/insights/api/common/rbac/service.rb +1 -1
- data/lib/insights/api/common/request.rb +11 -0
- data/lib/insights/api/common/version.rb +1 -1
- metadata +35 -19
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 38345e2eca706bb365900e3e3eb7ea2488d38587b654536cfe60bc04367a9e8c
|
4
|
+
data.tar.gz: 101d18e408627530170f6b62013ed494970939fa40e46fecb8e38e7cf4493685
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fcf19c204af28d1a540408f86967e96e25e7345ffe0cdd1cfd9fc07b697cdbfba2abfee9fb9dca9f042c34988826dd3f2e000e57239fd3d1f5a19b1fd72309f8
|
7
|
+
data.tar.gz: a0bb8e4e1499fcdca95a925565fe601db6cc1a410c2c2d0c36b47ec3d766e248477bb67ecb842d4c79540a4cb3b49df332faa87c9913bb3e2851fe60a8e2a514
|
data/README.md
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
# Insights::API::Common
|
2
2
|
|
3
3
|
[](https://travis-ci.org/RedHatInsights/insights-api-common-rails)
|
4
|
-
[](https://codeclimate.com/github/RedHatInsights/insights-api-common-rails/maintainability)
|
5
|
+
[](https://codeclimate.com/github/RedHatInsights/insights-api-common-rails/test_coverage)
|
6
6
|
[](https://hakiri.io/github/RedHatInsights/insights-api-common-rails/master)
|
7
7
|
|
8
8
|
Header, Encryption, RBAC, Serialization, Pagination and other common behavior for Insights microservices built with Rails
|
data/lib/insights/api/common.rb
CHANGED
@@ -7,16 +7,28 @@ module Insights
|
|
7
7
|
|
8
8
|
def self.included(other)
|
9
9
|
other.rescue_from(StandardError, RuntimeError) do |exception|
|
10
|
-
|
11
|
-
|
12
|
-
exception_list_from(exception).each do |exc|
|
13
|
-
code = exc.respond_to?(:code) ? exc.code : error_code_from_class(exc)
|
14
|
-
error_document.add(code.to_s, "#{exc.class}: #{exc.message}")
|
15
|
-
end
|
10
|
+
rescue_from_handler(exception) do |error_document, exc|
|
11
|
+
error_document.add(error_code_from_class(exc).to_s, "#{exc.class}: #{exc.message}")
|
16
12
|
end
|
13
|
+
end
|
14
|
+
end
|
17
15
|
|
18
|
-
|
16
|
+
def rescue_from_handler(exception)
|
17
|
+
logger.error("#{exception.class.name}: #{exception.message}\n#{exception.backtrace.join("\n")}")
|
18
|
+
errors = Insights::API::Common::ErrorDocument.new.tap do |error_document|
|
19
|
+
exception_list_from(exception).each do |exc|
|
20
|
+
if api_client_exception?(exc)
|
21
|
+
api_client_errors(exc, error_document)
|
22
|
+
elsif custom_exception?(exc)
|
23
|
+
message = fetch_custom_message(exc)
|
24
|
+
error_document.add(error_code_from_class(exc).to_s, message)
|
25
|
+
else
|
26
|
+
yield error_document, exc
|
27
|
+
end
|
28
|
+
end
|
19
29
|
end
|
30
|
+
|
31
|
+
render :json => errors.to_h, :status => error_code_from_class(exception)
|
20
32
|
end
|
21
33
|
|
22
34
|
def exception_list_from(exception)
|
@@ -28,6 +40,14 @@ module Insights
|
|
28
40
|
end
|
29
41
|
end
|
30
42
|
|
43
|
+
def custom_exception?(exception)
|
44
|
+
Insights::API::Common::CustomExceptions::CUSTOM_EXCEPTION_LIST.include?(exception.class.to_s)
|
45
|
+
end
|
46
|
+
|
47
|
+
def fetch_custom_message(exception)
|
48
|
+
Insights::API::Common::CustomExceptions.custom_message(exception)
|
49
|
+
end
|
50
|
+
|
31
51
|
def error_code_from_class(exception)
|
32
52
|
if ActionDispatch::ExceptionWrapper.rescue_responses.key?(exception.class.to_s)
|
33
53
|
Rack::Utils.status_code(ActionDispatch::ExceptionWrapper.rescue_responses[exception.class.to_s])
|
@@ -35,6 +55,30 @@ module Insights
|
|
35
55
|
DEFAULT_ERROR_CODE
|
36
56
|
end
|
37
57
|
end
|
58
|
+
|
59
|
+
def api_client_exception?(exc)
|
60
|
+
exc.respond_to?(:code) && exc.respond_to?(:response_body) && exc.respond_to?(:response_headers) &&
|
61
|
+
!exc.response_body.nil?
|
62
|
+
end
|
63
|
+
|
64
|
+
def api_client_errors(exc, error_document)
|
65
|
+
body = json_parsed_body(exc)
|
66
|
+
if body.is_a?(Hash) && body.key?('errors') && body['errors'].is_a?(Array)
|
67
|
+
body['errors'].each do |error|
|
68
|
+
next unless error.key?('status') && error.key?('detail')
|
69
|
+
|
70
|
+
error_document.add(error['status'], error['detail'])
|
71
|
+
end
|
72
|
+
else
|
73
|
+
error_document.add(exc.code.to_s, exc.message )
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def json_parsed_body(exc)
|
78
|
+
JSON.parse(exc.response_body)
|
79
|
+
rescue StandardError
|
80
|
+
nil
|
81
|
+
end
|
38
82
|
end
|
39
83
|
end
|
40
84
|
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Insights
|
2
|
+
module API
|
3
|
+
module Common
|
4
|
+
class CustomExceptions
|
5
|
+
CUSTOM_EXCEPTION_LIST = %w[Pundit::NotAuthorizedError].freeze
|
6
|
+
|
7
|
+
def self.custom_message(exception)
|
8
|
+
case exception.class.to_s
|
9
|
+
when "Pundit::NotAuthorizedError"
|
10
|
+
"You are not authorized to #{exception.query.delete_suffix('?')} this #{exception.record.model_name.human.downcase}"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -2,21 +2,24 @@ module Insights
|
|
2
2
|
module API
|
3
3
|
module Common
|
4
4
|
class Metrics
|
5
|
-
def self.activate(config, prefix)
|
5
|
+
def self.activate(config, prefix, args = {})
|
6
6
|
require 'prometheus_exporter'
|
7
7
|
require 'prometheus_exporter/client'
|
8
8
|
|
9
|
+
return if metrics_port == 0
|
10
|
+
|
9
11
|
ensure_exporter_server
|
10
12
|
enable_in_process_metrics
|
11
13
|
enable_web_server_metrics(prefix)
|
14
|
+
setup_custom_metrics(args[:custom_metrics])
|
12
15
|
end
|
13
16
|
|
14
17
|
private_class_method def self.ensure_exporter_server
|
15
18
|
require 'socket'
|
16
|
-
TCPSocket.open("localhost",
|
19
|
+
TCPSocket.open("localhost", metrics_port) {}
|
17
20
|
rescue Errno::ECONNREFUSED
|
18
21
|
require 'prometheus_exporter/server'
|
19
|
-
server = PrometheusExporter::Server::WebServer.new(port:
|
22
|
+
server = PrometheusExporter::Server::WebServer.new(port: metrics_port)
|
20
23
|
server.start
|
21
24
|
|
22
25
|
PrometheusExporter::Client.default = PrometheusExporter::LocalClient.new(collector: server.collector)
|
@@ -33,6 +36,27 @@ module Insights
|
|
33
36
|
require "insights/api/common/middleware/web_server_metrics"
|
34
37
|
Rails.application.middleware.unshift(Insights::API::Common::Middleware::WebServerMetrics, :metrics_prefix => prefix)
|
35
38
|
end
|
39
|
+
|
40
|
+
private_class_method def self.setup_custom_metrics(custom_metrics)
|
41
|
+
return if custom_metrics.nil?
|
42
|
+
|
43
|
+
custom_metrics.each do |metric|
|
44
|
+
instance_variable_set("@#{metric[:name]}_#{metric[:type]}", PrometheusExporter::Client.default.register(metric[:type], metric[:name], metric[:description]))
|
45
|
+
|
46
|
+
define_singleton_method(metric[:name]) do
|
47
|
+
case metric[:type]
|
48
|
+
when :counter
|
49
|
+
instance_variable_get("@#{metric[:name]}_#{metric[:type]}")&.observe(1)
|
50
|
+
else
|
51
|
+
"Metric of type #{metric[:type]} unsupported, implement it in Insights::API::Common::Metrics#L45"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
private_class_method def self.metrics_port
|
58
|
+
@metrics_port ||= (ENV['METRICS_PORT']&.to_i || 9394)
|
59
|
+
end
|
36
60
|
end
|
37
61
|
end
|
38
62
|
end
|
@@ -48,6 +48,7 @@ module Insights
|
|
48
48
|
ENV['APP_NAME'] = app_name
|
49
49
|
ENV['PATH_PREFIX'] = app_prefix
|
50
50
|
Rails.application.reload_routes!
|
51
|
+
@operation_id_hash = {}
|
51
52
|
end
|
52
53
|
|
53
54
|
def server_base_path
|
@@ -117,8 +118,8 @@ module Insights
|
|
117
118
|
"type" => "object",
|
118
119
|
"properties" => {
|
119
120
|
"status" => {
|
120
|
-
"type" => "
|
121
|
-
"example" => 404
|
121
|
+
"type" => "string",
|
122
|
+
"example" => "404"
|
122
123
|
},
|
123
124
|
"detail" => {
|
124
125
|
"type" => "string",
|
@@ -200,7 +201,7 @@ module Insights
|
|
200
201
|
sub_collection = (primary_collection != klass_name)
|
201
202
|
{
|
202
203
|
"summary" => "List #{klass_name.pluralize}#{" for #{primary_collection}" if sub_collection}",
|
203
|
-
"operationId" =>
|
204
|
+
"operationId" => operation_id(klass_name, primary_collection, sub_collection),
|
204
205
|
"description" => "Returns an array of #{klass_name} objects",
|
205
206
|
"parameters" => [
|
206
207
|
{ "$ref" => "##{PARAMETERS_PATH}/QueryLimit" },
|
@@ -562,6 +563,24 @@ module Insights
|
|
562
563
|
def schema_overrides
|
563
564
|
{}
|
564
565
|
end
|
566
|
+
|
567
|
+
def validate_operation_id(name, klass_name)
|
568
|
+
if @operation_id_hash.key?(name)
|
569
|
+
raise ArgumentError, "operation id cannot be duplicates, #{name} in class #{klass_name} has already been used in class #{@operation_id_hash[name]}"
|
570
|
+
end
|
571
|
+
@operation_id_hash[name] = klass_name
|
572
|
+
end
|
573
|
+
|
574
|
+
def operation_id(klass_name, primary_collection, sub_collection)
|
575
|
+
klass = klass_name.constantize
|
576
|
+
name = if klass.respond_to?(:list_operation_id)
|
577
|
+
klass.send(:list_operation_id)
|
578
|
+
else
|
579
|
+
"list#{primary_collection if sub_collection}#{klass_name.pluralize}"
|
580
|
+
end
|
581
|
+
validate_operation_id(name, klass_name)
|
582
|
+
name
|
583
|
+
end
|
565
584
|
end
|
566
585
|
end
|
567
586
|
end
|
@@ -3,27 +3,40 @@ module Insights
|
|
3
3
|
module Common
|
4
4
|
module OpenApi
|
5
5
|
module Serializer
|
6
|
+
include VersionFromPrefix
|
7
|
+
|
6
8
|
def as_json(arg = {})
|
7
|
-
previous = super
|
9
|
+
previous = super(:except => _excluded_attributes(arg))
|
10
|
+
|
8
11
|
encrypted_columns_set = (self.class.try(:encrypted_columns) || []).to_set
|
9
12
|
encryption_filtered = previous.except(*encrypted_columns_set)
|
10
13
|
return encryption_filtered unless arg.key?(:prefixes)
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
attrs = encryption_filtered.slice(*schema["properties"].keys)
|
15
|
-
schema["properties"].keys.each do |name|
|
14
|
+
|
15
|
+
attrs = encryption_filtered.slice(*_schema_properties(arg).keys)
|
16
|
+
_schema_properties(arg).keys.each do |name|
|
16
17
|
next if attrs[name].nil?
|
17
18
|
attrs[name] = attrs[name].iso8601 if attrs[name].kind_of?(Time)
|
18
19
|
attrs[name] = attrs[name].to_s if name.ends_with?("_id") || name == "id"
|
19
|
-
attrs[name] = self.public_send(name) if !attrs.key?(name) && !encrypted_columns_set.include?(name)
|
20
20
|
end
|
21
21
|
attrs.compact
|
22
22
|
end
|
23
23
|
|
24
|
-
|
25
|
-
|
26
|
-
|
24
|
+
private
|
25
|
+
|
26
|
+
def _excluded_attributes(arg)
|
27
|
+
return [] unless arg.key?(:prefixes)
|
28
|
+
|
29
|
+
self.attributes.keys - _schema_properties(arg).keys
|
30
|
+
end
|
31
|
+
|
32
|
+
def _schema_properties(arg)
|
33
|
+
@schema_properties ||= _schema(arg)["properties"]
|
34
|
+
end
|
35
|
+
|
36
|
+
def _schema(arg)
|
37
|
+
version = api_version_from_prefix(arg[:prefixes].first)
|
38
|
+
presentation_name = self.class.try(:presentation_name) || self.class.name
|
39
|
+
::Insights::API::Common::OpenApi::Docs.instance[version].definitions[presentation_name]
|
27
40
|
end
|
28
41
|
end
|
29
42
|
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Insights
|
2
|
+
module API
|
3
|
+
module Common
|
4
|
+
module OpenApi
|
5
|
+
module VersionFromPrefix
|
6
|
+
def api_version_from_prefix(prefix)
|
7
|
+
/\/?\w+\/v(?<major>\d+)[x\.]?(?<minor>\d+)?\// =~ prefix
|
8
|
+
[major, minor].compact.join(".")
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -25,6 +25,14 @@ module Insights
|
|
25
25
|
Thread.current[:current_request]
|
26
26
|
end
|
27
27
|
|
28
|
+
def self.current_request_id
|
29
|
+
Thread.current[:request_id]
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.current_request_id=(id)
|
33
|
+
Thread.current[:request_id] = id
|
34
|
+
end
|
35
|
+
|
28
36
|
def self.current!
|
29
37
|
current || raise(RequestNotSet)
|
30
38
|
end
|
@@ -45,10 +53,13 @@ module Insights
|
|
45
53
|
|
46
54
|
def self.with_request(request)
|
47
55
|
saved = current
|
56
|
+
saved_request_id = current&.request_id
|
48
57
|
self.current = request
|
58
|
+
self.current_request_id = current&.request_id
|
49
59
|
yield current
|
50
60
|
ensure
|
51
61
|
self.current = saved
|
62
|
+
self.current_request_id = saved_request_id
|
52
63
|
end
|
53
64
|
|
54
65
|
def self.current_forwardable
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: insights-api-common
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 4.
|
4
|
+
version: 4.1.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Insights Authors
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-06-09 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: acts_as_tenant
|
@@ -56,22 +56,22 @@ dependencies:
|
|
56
56
|
name: rails
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
|
-
- - ">="
|
60
|
-
- !ruby/object:Gem::Version
|
61
|
-
version: 5.2.1.1
|
62
59
|
- - "~>"
|
63
60
|
- !ruby/object:Gem::Version
|
64
|
-
version:
|
61
|
+
version: 5.2.2
|
62
|
+
- - ">="
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: 5.2.2.1
|
65
65
|
type: :runtime
|
66
66
|
prerelease: false
|
67
67
|
version_requirements: !ruby/object:Gem::Requirement
|
68
68
|
requirements:
|
69
|
-
- - ">="
|
70
|
-
- !ruby/object:Gem::Version
|
71
|
-
version: 5.2.1.1
|
72
69
|
- - "~>"
|
73
70
|
- !ruby/object:Gem::Version
|
74
|
-
version:
|
71
|
+
version: 5.2.2
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: 5.2.2.1
|
75
75
|
- !ruby/object:Gem::Dependency
|
76
76
|
name: manageiq-loggers
|
77
77
|
requirement: !ruby/object:Gem::Requirement
|
@@ -142,6 +142,20 @@ dependencies:
|
|
142
142
|
- - "~>"
|
143
143
|
- !ruby/object:Gem::Version
|
144
144
|
version: 0.10.0
|
145
|
+
- !ruby/object:Gem::Dependency
|
146
|
+
name: insights-rbac-api-client
|
147
|
+
requirement: !ruby/object:Gem::Requirement
|
148
|
+
requirements:
|
149
|
+
- - "~>"
|
150
|
+
- !ruby/object:Gem::Version
|
151
|
+
version: '1.0'
|
152
|
+
type: :runtime
|
153
|
+
prerelease: false
|
154
|
+
version_requirements: !ruby/object:Gem::Requirement
|
155
|
+
requirements:
|
156
|
+
- - "~>"
|
157
|
+
- !ruby/object:Gem::Version
|
158
|
+
version: '1.0'
|
145
159
|
- !ruby/object:Gem::Dependency
|
146
160
|
name: graphql
|
147
161
|
requirement: !ruby/object:Gem::Requirement
|
@@ -222,16 +236,16 @@ dependencies:
|
|
222
236
|
name: rake
|
223
237
|
requirement: !ruby/object:Gem::Requirement
|
224
238
|
requirements:
|
225
|
-
- - "
|
239
|
+
- - ">="
|
226
240
|
- !ruby/object:Gem::Version
|
227
|
-
version:
|
241
|
+
version: 12.3.3
|
228
242
|
type: :development
|
229
243
|
prerelease: false
|
230
244
|
version_requirements: !ruby/object:Gem::Requirement
|
231
245
|
requirements:
|
232
|
-
- - "
|
246
|
+
- - ">="
|
233
247
|
- !ruby/object:Gem::Version
|
234
|
-
version:
|
248
|
+
version: 12.3.3
|
235
249
|
- !ruby/object:Gem::Dependency
|
236
250
|
name: rspec
|
237
251
|
requirement: !ruby/object:Gem::Requirement
|
@@ -278,16 +292,16 @@ dependencies:
|
|
278
292
|
name: simplecov
|
279
293
|
requirement: !ruby/object:Gem::Requirement
|
280
294
|
requirements:
|
281
|
-
- - "
|
295
|
+
- - "~>"
|
282
296
|
- !ruby/object:Gem::Version
|
283
|
-
version:
|
297
|
+
version: 0.17.1
|
284
298
|
type: :development
|
285
299
|
prerelease: false
|
286
300
|
version_requirements: !ruby/object:Gem::Requirement
|
287
301
|
requirements:
|
288
|
-
- - "
|
302
|
+
- - "~>"
|
289
303
|
- !ruby/object:Gem::Version
|
290
|
-
version:
|
304
|
+
version: 0.17.1
|
291
305
|
- !ruby/object:Gem::Dependency
|
292
306
|
name: webmock
|
293
307
|
requirement: !ruby/object:Gem::Requirement
|
@@ -331,6 +345,7 @@ files:
|
|
331
345
|
- lib/insights/api/common/application_controller_mixins/request_body_validation.rb
|
332
346
|
- lib/insights/api/common/application_controller_mixins/request_parameter_validation.rb
|
333
347
|
- lib/insights/api/common/application_controller_mixins/request_path.rb
|
348
|
+
- lib/insights/api/common/custom_exceptions.rb
|
334
349
|
- lib/insights/api/common/engine.rb
|
335
350
|
- lib/insights/api/common/entitlement.rb
|
336
351
|
- lib/insights/api/common/error_document.rb
|
@@ -357,6 +372,7 @@ files:
|
|
357
372
|
- lib/insights/api/common/open_api/docs/object_definition.rb
|
358
373
|
- lib/insights/api/common/open_api/generator.rb
|
359
374
|
- lib/insights/api/common/open_api/serializer.rb
|
375
|
+
- lib/insights/api/common/open_api/version_from_prefix.rb
|
360
376
|
- lib/insights/api/common/option_redirect_enhancements.rb
|
361
377
|
- lib/insights/api/common/paginated_response.rb
|
362
378
|
- lib/insights/api/common/paginated_response_v2.rb
|
@@ -399,7 +415,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
399
415
|
- !ruby/object:Gem::Version
|
400
416
|
version: '0'
|
401
417
|
requirements: []
|
402
|
-
rubygems_version: 3.
|
418
|
+
rubygems_version: 3.0.3
|
403
419
|
signing_key:
|
404
420
|
specification_version: 4
|
405
421
|
summary: Common Utilites for Insights microservices
|