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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5f6030728902f58dcb0ce11d0de032c9c3ab442877686e097a83a7c6a7da2cfe
4
- data.tar.gz: ef7838a59298b7ef0a5c0b4aa6681331f4757fc073cebbdc791f154c28c05fe9
3
+ metadata.gz: 38345e2eca706bb365900e3e3eb7ea2488d38587b654536cfe60bc04367a9e8c
4
+ data.tar.gz: 101d18e408627530170f6b62013ed494970939fa40e46fecb8e38e7cf4493685
5
5
  SHA512:
6
- metadata.gz: bd8564c491431e177fa89daa27330dcbfe02ddfdeaa915eb6207a32fe659257cda7684ef39d0c05e8732c404a3bf3c54617b3f563c4054b170f06ac9901230e4
7
- data.tar.gz: 90c76dc626605e9d9c890203832213751938ae6a45dedeeac4b5d30ce3e76804a166b894be4281549a5169495920d8e6de17446aaf2645ef706b4b19fc7e4697
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
  [![Build Status](https://travis-ci.org/RedHatInsights/insights-api-common-rails.svg)](https://travis-ci.org/RedHatInsights/insights-api-common-rails)
4
- [![Maintainability](https://api.codeclimate.com/v1/badges/790ea6c77d82da6be68a/maintainability)](https://codeclimate.com/github/RedHatInsights/insights-api-common-rails/maintainability)
5
- [![Test Coverage](https://api.codeclimate.com/v1/badges/790ea6c77d82da6be68a/test_coverage)](https://codeclimate.com/github/RedHatInsights/insights-api-common-rails/test_coverage)
4
+ [![Maintainability](https://api.codeclimate.com/v1/badges/8ec8a19165383fdaed47/maintainability)](https://codeclimate.com/github/RedHatInsights/insights-api-common-rails/maintainability)
5
+ [![Test Coverage](https://api.codeclimate.com/v1/badges/8ec8a19165383fdaed47/test_coverage)](https://codeclimate.com/github/RedHatInsights/insights-api-common-rails/test_coverage)
6
6
  [![Security](https://hakiri.io/github/RedHatInsights/insights-api-common-rails/master.svg)](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
@@ -1,3 +1,4 @@
1
+ require "insights/api/common/custom_exceptions"
1
2
  require "insights/api/common/engine"
2
3
  require "insights/api/common/entitlement"
3
4
  require "insights/api/common/error_document"
@@ -7,16 +7,28 @@ module Insights
7
7
 
8
8
  def self.included(other)
9
9
  other.rescue_from(StandardError, RuntimeError) do |exception|
10
- logger.error("#{exception.class.name}: #{exception.message}\n#{exception.backtrace.join("\n")}")
11
- errors = Insights::API::Common::ErrorDocument.new.tap do |error_document|
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
- render :json => errors.to_h, :status => error_code_from_class(exception)
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", 9394) {}
19
+ TCPSocket.open("localhost", metrics_port) {}
17
20
  rescue Errno::ECONNREFUSED
18
21
  require 'prometheus_exporter/server'
19
- server = PrometheusExporter::Server::WebServer.new(port: 9394)
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
@@ -1,2 +1,3 @@
1
1
  require "insights/api/common/open_api/docs"
2
+ require "insights/api/common/open_api/version_from_prefix"
2
3
  require "insights/api/common/open_api/serializer"
@@ -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" => "integer",
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" => "list#{primary_collection if sub_collection}#{klass_name.pluralize}",
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
- version = api_version_from_prefix(arg[:prefixes].first)
12
- presentation_name = self.class.try(:presentation_name) || self.class.name
13
- schema = ::Insights::API::Common::OpenApi::Docs.instance[version].definitions[presentation_name]
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
- def api_version_from_prefix(prefix)
25
- /\/?\w+\/v(?<major>\d+)[x\.]?(?<minor>\d+)?\// =~ prefix
26
- [major, minor].compact.join(".")
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
@@ -2,7 +2,7 @@ module Insights
2
2
  module API
3
3
  module Common
4
4
  module RBAC
5
- require 'rbac-api-client'
5
+ require 'insights-rbac-api-client'
6
6
 
7
7
  class Seed
8
8
  def initialize(seed_file, user_file = nil)
@@ -2,7 +2,7 @@ module Insights
2
2
  module API
3
3
  module Common
4
4
  module RBAC
5
- require 'rbac-api-client'
5
+ require 'insights-rbac-api-client'
6
6
 
7
7
  class NetworkError < StandardError; end
8
8
  class TimedOutError < StandardError; 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
@@ -1,7 +1,7 @@
1
1
  module Insights
2
2
  module API
3
3
  module Common
4
- VERSION = "4.0.0".freeze
4
+ VERSION = "4.1.1".freeze
5
5
  end
6
6
  end
7
7
  end
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.0.0
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-04-15 00:00:00.000000000 Z
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: '5.2'
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: '5.2'
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: '10.0'
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: '10.0'
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: '0'
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: '0'
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.1.2
418
+ rubygems_version: 3.0.3
403
419
  signing_key:
404
420
  specification_version: 4
405
421
  summary: Common Utilites for Insights microservices