insights-api-common 4.0.0 → 4.1.1

Sign up to get free protection for your applications and to get access to all the features.
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