topological_inventory-providers-common 1.0.12 → 2.0.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: b81c523169c3ca87e678e3e7d046b4daee863200cf4eadd695e9022d17a11e5a
4
- data.tar.gz: 1fc2a47cedf6f8d3b41f0df728a5ac8ceceaea03b58d43ef67da90680861c0eb
3
+ metadata.gz: 134e945b201072b8bdd7fc6af73b04a6f60698da831ef3989ab973453a1a5159
4
+ data.tar.gz: 20e4f260c411551572b557f4e4454fdc03c9e3b2ac9c8f5997e6600780298c1a
5
5
  SHA512:
6
- metadata.gz: 9afb8ba326d632ba59245d93837f32bd8edbcb34b3d82e029c351f904bb75f196592eb4724116a48caf0b7df8fc7f4101f5b59bd0e23b65d6fca25ada3ac4cb6
7
- data.tar.gz: '00924d04d1fc4623cd4e0f0a719c5471dbd39635820375b4957278db1c6e3ecbdf2b58c25758bca22e91e979511286708279922919f6303118401e58ac9cc5c4'
6
+ metadata.gz: 0a847566f6fb20e9518faef660c626788e6afd20d26cec28a5899e645c68cedd7c017974d0cb1c3c2524253007386b9728a1bb67a0f0a9101fe62bd388f03ccb
7
+ data.tar.gz: 6e786659b1bf913fe1e22d9c0618986d03dc4bc169d782e446ecd089b97a136b639f83be85c562006dccde83a8d025b102863b565aa571746767fa292e4dae89
@@ -4,17 +4,20 @@ All notable changes to this project will be documented in this file.
4
4
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
5
5
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [2.0.0]
8
+ Operations/API clients refactoring
9
+
7
10
  ## [1.0.12] - 2020-10-01
8
11
  Add Operations Async Worker class #55
9
12
 
10
- ## [1.0.11]
13
+ ## [1.0.11] - 2020-09-04
11
14
  Make Collector Poll Time a parameter so we can tweak the collection interval #51
12
15
 
13
- ## [1.0.10]
16
+ ## [1.0.10] - 2020-08-26
14
17
  Add HealthCheck class for operations workers #48
15
18
  Set the LOG_LEVEL if present #50
16
19
 
17
- ## [1.0.9]
20
+ ## [1.0.9] - 2020-08-17
18
21
  Added refresh-type to save and sweep inventory #45
19
22
 
20
23
  ## [1.0.8] - 2020-08-12
@@ -59,7 +62,8 @@ manageiq-loggers to >= 0.4.2 #20
59
62
  ## [1.0.0] - 2020-03-19
60
63
  ### Initial release to rubygems.org
61
64
 
62
- [Unreleased]: https://github.com/RedHatInsights/topological_inventory-providers-common/compare/v1.0.12...HEAD
65
+ [Unreleased]: https://github.com/RedHatInsights/topological_inventory-providers-common/compare/v2.0.0...HEAD
66
+ [2.0.0]: https://github.com/RedHatInsights/topological_inventory-providers-common/compare/v1.0.12...v2.0.0
63
67
  [1.0.12]: https://github.com/RedHatInsights/topological_inventory-providers-common/compare/v1.0.11...v1.0.12
64
68
  [1.0.11]: https://github.com/RedHatInsights/topological_inventory-providers-common/compare/v1.0.10...v1.0.11
65
69
  [1.0.10]: https://github.com/RedHatInsights/topological_inventory-providers-common/compare/v1.0.9...v1.0.10
@@ -1,7 +1,5 @@
1
1
  require "topological_inventory/providers/common/version"
2
2
  require "topological_inventory/providers/common/logging"
3
- require "topological_inventory/providers/common/operations/processor"
4
- require "topological_inventory/providers/common/operations/endpoint_client"
5
3
  require "topological_inventory/providers/common/operations/health_check"
6
4
  require "topological_inventory/providers/common/collectors_pool"
7
5
  require "topological_inventory/providers/common/collector"
@@ -23,11 +23,14 @@ module TopologicalInventory
23
23
  end
24
24
 
25
25
  def availability_check(message, severity = :info)
26
- log_with_prefix("Source#availability_check", message, severity)
26
+ send("#{severity}_ext", "Source#availability_check", message)
27
27
  end
28
28
 
29
- def log_with_prefix(prefix, message, severity)
30
- send(severity, "#{prefix} - #{message}") if respond_to?(severity)
29
+ %w[debug info warn error fatal].each do |severity|
30
+ define_method("#{severity}_ext".to_sym) do |prefix, message|
31
+ ext_message = [prefix, message].compact.join(' - ')
32
+ send(severity, ext_message)
33
+ end
31
34
  end
32
35
  end
33
36
 
@@ -0,0 +1,58 @@
1
+ require "topological_inventory/providers/common/sources_api_client"
2
+
3
+ module TopologicalInventory
4
+ module Providers
5
+ module Common
6
+ module Mixins
7
+ module SourcesApi
8
+ AUTH_NOT_NECESSARY = "n/a".freeze
9
+
10
+ def sources_api
11
+ @sources_api ||= TopologicalInventory::Providers::Common::SourcesApiClient.new(identity)
12
+ end
13
+
14
+ def endpoint
15
+ @endpoint ||= sources_api.fetch_default_endpoint(source_id)
16
+ rescue => e
17
+ logger.error_ext(operation, "Failed to fetch Endpoint for Source #{source_id}: #{e.message}")
18
+ nil
19
+ end
20
+
21
+ def authentication
22
+ @authentication ||= if endpoint.receptor_node.present?
23
+ AUTH_NOT_NECESSARY
24
+ else
25
+ sources_api.fetch_authentication(source_id, endpoint)
26
+ end
27
+ rescue => e
28
+ logger.error_ext(operation, "Failed to fetch Authentication for Source #{source_id}: #{e.message}")
29
+ nil
30
+ end
31
+
32
+ def application
33
+ @application ||= sources_api.fetch_application(source_id)
34
+ rescue => e
35
+ logger.error_ext(operation, "Failed to fetch Application for Source #{source_id}: #{e.message}")
36
+ nil
37
+ end
38
+
39
+ def on_premise?
40
+ @on_premise ||= endpoint&.receptor_node.to_s.strip.present?
41
+ end
42
+
43
+ def verify_ssl_mode
44
+ endpoint&.verify_ssl ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE
45
+ end
46
+
47
+ def full_hostname(endpoint)
48
+ if on_premise?
49
+ "receptor://#{endpoint.receptor_node}"
50
+ else
51
+ endpoint.host.tap { |host| host << ":#{endpoint.port}" if endpoint.port }
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,26 @@
1
+ require "topological_inventory/providers/common/topology_api_client"
2
+
3
+ module TopologicalInventory
4
+ module Providers
5
+ module Common
6
+ module Mixins
7
+ module TopologyApi
8
+ # @identity attr_reader is expected
9
+ def topology_api
10
+ @topology_api ||= TopologicalInventory::Providers::Common::TopologyApiClient.new(identity)
11
+ end
12
+
13
+ def update_task(task_id, source_id: nil, state:, status:, target_type: nil, target_source_ref: nil, context: nil)
14
+ topology_api.update_task(task_id,
15
+ :source_id => source_id,
16
+ :state => state,
17
+ :status => status,
18
+ :target_type => target_type,
19
+ :target_source_ref => target_source_ref,
20
+ :context => context)
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,24 @@
1
+ module TopologicalInventory
2
+ module Providers
3
+ module Common
4
+ module Mixins
5
+ module XRhHeaders
6
+ def account_number_by_identity(identity)
7
+ return @account_number if @account_number
8
+ return if identity.try(:[], 'x-rh-identity').nil?
9
+
10
+ identity_hash = JSON.parse(Base64.decode64(identity['x-rh-identity']))
11
+ @account_number = identity_hash.dig('identity', 'account_number')
12
+ rescue JSON::ParserError => e
13
+ logger.error_ext(operation, "Failed to parse identity header: #{e.message}")
14
+ nil
15
+ end
16
+
17
+ def identity_by_account_number(account_number)
18
+ @identity ||= {"x-rh-identity" => Base64.strict_encode64({"identity" => {"account_number" => account_number, "user" => {"is_org_admin" => true}}}.to_json)}
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -1,194 +1,169 @@
1
1
  require "topological_inventory/providers/common/logging"
2
2
  require "active_support/core_ext/numeric/time"
3
- require "topological_inventory/providers/common/operations/sources_api_client"
3
+ require "topological_inventory/providers/common/mixins/sources_api"
4
+ require "topological_inventory/providers/common/mixins/x_rh_headers"
4
5
 
5
6
  module TopologicalInventory
6
7
  module Providers
7
- module Common
8
- module Operations
9
- class Source
10
- include Logging
8
+ module Common
9
+ module Operations
10
+ class Source
11
+ include Logging
12
+ include Mixins::SourcesApi
13
+ include Mixins::XRhHeaders
11
14
 
12
- STATUS_AVAILABLE, STATUS_UNAVAILABLE = %w[available unavailable].freeze
15
+ STATUS_AVAILABLE, STATUS_UNAVAILABLE = %w[available unavailable].freeze
13
16
 
14
- ERROR_MESSAGES = {
15
- :authentication_not_found => "Authentication not found in Sources API",
16
- :endpoint_or_application_not_found => "Endpoint or Application not found in Sources API",
17
- }.freeze
17
+ ERROR_MESSAGES = {
18
+ :authentication_not_found => "Authentication not found in Sources API",
19
+ :endpoint_or_application_not_found => "Endpoint or Application not found in Sources API",
20
+ }.freeze
18
21
 
19
- LAST_CHECKED_AT_THRESHOLD = 5.minutes.freeze
20
- AUTH_NOT_NECESSARY = "n/a".freeze
22
+ LAST_CHECKED_AT_THRESHOLD = 5.minutes.freeze
23
+ AUTH_NOT_NECESSARY = "n/a".freeze
21
24
 
22
- attr_accessor :params, :request_context, :source_id, :account_number
25
+ attr_accessor :identity, :operation, :params, :request_context, :source_id, :account_number
23
26
 
24
- def initialize(params = {}, request_context = nil)
25
- self.params = params
26
- self.request_context = request_context
27
- self.source_id = params['source_id']
28
- self.account_number = params['external_tenant']
29
- end
27
+ def initialize(params = {}, request_context = nil)
28
+ self.operation = 'Source'
29
+ self.params = params
30
+ self.request_context = request_context
31
+ self.source_id = params['source_id']
30
32
 
31
- def availability_check
32
- return if params_missing?
33
+ self.account_number = params['external_tenant']
34
+ self.identity = identity_by_account_number(account_number)
35
+ end
33
36
 
34
- return if checked_recently?
37
+ def availability_check
38
+ self.operation += '#availability_check'
35
39
 
36
- status, error_message = connection_status
40
+ return if params_missing?
37
41
 
38
- update_source_and_subresources(status, error_message)
42
+ return if checked_recently?
39
43
 
40
- logger.availability_check("Completed: Source #{source_id} is #{status}")
41
- end
44
+ status, error_message = connection_status
42
45
 
43
- private
44
-
45
- def required_params
46
- %w[source_id]
47
- end
46
+ update_source_and_subresources(status, error_message)
48
47
 
49
- def params_missing?
50
- is_missing = false
51
- required_params.each do |attr|
52
- if (is_missing = params[attr].blank?)
53
- logger.availability_check("Missing #{attr} for the availability_check request [Source ID: #{source_id}]", :error)
54
- break
55
- end
48
+ logger.availability_check("Completed: Source #{source_id} is #{status}")
56
49
  end
57
50
 
58
- is_missing
59
- end
60
-
61
- def checked_recently?
62
- checked_recently = if endpoint.present?
63
- endpoint.last_checked_at.present? && endpoint.last_checked_at >= LAST_CHECKED_AT_THRESHOLD.ago
64
- elsif application.present?
65
- application.last_checked_at.present? && application.last_checked_at >= LAST_CHECKED_AT_THRESHOLD.ago
66
- end
51
+ private
67
52
 
68
- logger.availability_check("Skipping, last check at #{endpoint.last_checked_at || application.last_checked_at} [Source ID: #{source_id}] ") if checked_recently
69
-
70
- checked_recently
71
- end
53
+ def required_params
54
+ %w[source_id]
55
+ end
72
56
 
73
- def connection_status
74
- # we need either an endpoint or application to check the source.
75
- return [STATUS_UNAVAILABLE, ERROR_MESSAGES[:endpoint_or_application_not_found]] unless endpoint || application
57
+ def params_missing?
58
+ is_missing = false
59
+ required_params.each do |attr|
60
+ if (is_missing = params[attr].blank?)
61
+ logger.availability_check("Missing #{attr} for the availability_check request [Source ID: #{source_id}]", :error)
62
+ break
63
+ end
64
+ end
76
65
 
77
- check_time
78
- if endpoint
79
- endpoint_connection_check
80
- elsif application
81
- application_connection_check
66
+ is_missing
82
67
  end
83
- end
84
68
 
85
- def endpoint_connection_check
86
- return [STATUS_UNAVAILABLE, ERROR_MESSAGES[:authentication_not_found]] unless authentication
69
+ def checked_recently?
70
+ checked_recently = if endpoint.present?
71
+ endpoint.last_checked_at.present? && endpoint.last_checked_at >= LAST_CHECKED_AT_THRESHOLD.ago
72
+ elsif application.present?
73
+ application.last_checked_at.present? && application.last_checked_at >= LAST_CHECKED_AT_THRESHOLD.ago
74
+ end
87
75
 
88
- # call down into the operations pod implementation of `Source#connection_check`
89
- connection_check
90
- end
76
+ logger.availability_check("Skipping, last check at #{endpoint.last_checked_at || application.last_checked_at} [Source ID: #{source_id}] ") if checked_recently
91
77
 
92
- def application_connection_check
93
- case application.availability_status
94
- when "available"
95
- [STATUS_AVAILABLE, nil]
96
- when "unavailable"
97
- [STATUS_UNAVAILABLE, "Application id #{application.id} unavailable"]
78
+ checked_recently
98
79
  end
99
- end
100
80
 
101
- # @return [Array<String, String|nil] - STATUS_[UN]AVAILABLE, error message
102
- def connection_check
103
- raise NotImplementedError, "#{__method__} must be implemented in a subclass"
104
- end
105
-
106
- def update_source_and_subresources(status, error_message = nil)
107
- logger.availability_check("Updating source [#{source_id}] status [#{status}] message [#{error_message}]")
81
+ def connection_status
82
+ # we need either an endpoint or application to check the source.
83
+ return [STATUS_UNAVAILABLE, ERROR_MESSAGES[:endpoint_or_application_not_found]] unless endpoint || application
108
84
 
109
- update_source(status)
85
+ check_time
86
+ if endpoint
87
+ endpoint_connection_check
88
+ elsif application
89
+ application_connection_check
90
+ end
91
+ end
110
92
 
111
- update_endpoint(status, error_message) if endpoint
112
- update_application(status) if application
113
- end
93
+ def endpoint_connection_check
94
+ return [STATUS_UNAVAILABLE, ERROR_MESSAGES[:authentication_not_found]] unless authentication
114
95
 
115
- def update_source(status)
116
- source = ::SourcesApiClient::Source.new
117
- source.availability_status = status
118
- source.last_checked_at = check_time
119
- source.last_available_at = check_time if status == STATUS_AVAILABLE
96
+ # call down into the operations pod implementation of `Source#connection_check`
97
+ connection_check
98
+ end
120
99
 
121
- api_client.update_source(source_id, source)
122
- rescue ::SourcesApiClient::ApiError => e
123
- logger.availability_check("Failed to update Source id:#{source_id} - #{e.message}", :error)
124
- end
100
+ def application_connection_check
101
+ case application.availability_status
102
+ when "available"
103
+ [STATUS_AVAILABLE, nil]
104
+ when "unavailable"
105
+ [STATUS_UNAVAILABLE, "Application id #{application.id} unavailable"]
106
+ end
107
+ end
125
108
 
126
- def update_endpoint(status, error_message)
127
- if endpoint.nil?
128
- logger.availability_check("Failed to update Endpoint for Source id:#{source_id}. Endpoint not found", :error)
129
- return
109
+ # @return [Array<String, String|nil] - STATUS_[UN]AVAILABLE, error message
110
+ def connection_check
111
+ raise NotImplementedError, "#{__method__} must be implemented in a subclass"
130
112
  end
131
113
 
132
- endpoint_update = ::SourcesApiClient::Endpoint.new
114
+ def update_source_and_subresources(status, error_message = nil)
115
+ logger.availability_check("Updating source [#{source_id}] status [#{status}] message [#{error_message}]")
133
116
 
134
- endpoint_update.availability_status = status
135
- endpoint_update.availability_status_error = error_message.to_s
136
- endpoint_update.last_checked_at = check_time
137
- endpoint_update.last_available_at = check_time if status == STATUS_AVAILABLE
117
+ update_source(status)
138
118
 
139
- api_client.update_endpoint(endpoint.id, endpoint_update)
140
- rescue ::SourcesApiClient::ApiError => e
141
- logger.availability_check("Failed to update Endpoint(ID: #{endpoint.id}) - #{e.message}", :error)
142
- end
119
+ update_endpoint(status, error_message) if endpoint
120
+ update_application(status) if application
121
+ end
143
122
 
144
- def update_application(status)
145
- application_update = ::SourcesApiClient::Application.new
146
- application_update.last_checked_at = check_time
147
- application_update.last_available_at = check_time if status == STATUS_AVAILABLE
123
+ def update_source(status)
124
+ source = ::SourcesApiClient::Source.new
125
+ source.availability_status = status
126
+ source.last_checked_at = check_time
127
+ source.last_available_at = check_time if status == STATUS_AVAILABLE
148
128
 
149
- api_client.update_application(application.id, application_update)
150
- rescue ::SourcesApiClient::ApiError => e
151
- logger.availability_check("Failed to update Application id: #{application.id} - #{e.message}", :error)
152
- end
129
+ sources_api.update_source(source_id, source)
130
+ rescue ::SourcesApiClient::ApiError => e
131
+ logger.availability_check("Failed to update Source id:#{source_id} - #{e.message}", :error)
132
+ end
153
133
 
154
- def endpoint
155
- @endpoint ||= api_client.fetch_default_endpoint(source_id)
156
- rescue => e
157
- logger.availability_check("Failed to fetch Endpoint for Source #{source_id}: #{e.message}", :error)
158
- nil
159
- end
134
+ def update_endpoint(status, error_message)
135
+ if endpoint.nil?
136
+ logger.availability_check("Failed to update Endpoint for Source id:#{source_id}. Endpoint not found", :error)
137
+ return
138
+ end
160
139
 
161
- def authentication
162
- @authentication ||= if endpoint.receptor_node.present?
163
- AUTH_NOT_NECESSARY
164
- else
165
- api_client.fetch_authentication(source_id, endpoint)
166
- end
167
- rescue => e
168
- logger.availability_check("Failed to fetch Authentication for Source #{source_id}: #{e.message}", :error)
169
- nil
170
- end
140
+ endpoint_update = ::SourcesApiClient::Endpoint.new
171
141
 
172
- def application
173
- @application ||= api_client.fetch_application(source_id)
174
- rescue => e
175
- logger.availability_check("Failed to fetch Application for Source #{source_id}: #{e.message}", :error)
176
- nil
177
- end
142
+ endpoint_update.availability_status = status
143
+ endpoint_update.availability_status_error = error_message.to_s
144
+ endpoint_update.last_checked_at = check_time
145
+ endpoint_update.last_available_at = check_time if status == STATUS_AVAILABLE
178
146
 
179
- def check_time
180
- @check_time ||= Time.now.utc
181
- end
147
+ sources_api.update_endpoint(endpoint.id, endpoint_update)
148
+ rescue ::SourcesApiClient::ApiError => e
149
+ logger.availability_check("Failed to update Endpoint(ID: #{endpoint.id}) - #{e.message}", :error)
150
+ end
182
151
 
183
- def identity
184
- @identity ||= {"x-rh-identity" => Base64.strict_encode64({"identity" => {"account_number" => account_number, "user" => {"is_org_admin" => true}}}.to_json)}
185
- end
152
+ def update_application(status)
153
+ application_update = ::SourcesApiClient::Application.new
154
+ application_update.last_checked_at = check_time
155
+ application_update.last_available_at = check_time if status == STATUS_AVAILABLE
186
156
 
187
- def api_client
188
- @api_client ||= TopologicalInventory::Providers::Common::Operations::SourcesApiClient.new(identity)
157
+ sources_api.update_application(application.id, application_update)
158
+ rescue ::SourcesApiClient::ApiError => e
159
+ logger.availability_check("Failed to update Application id: #{application.id} - #{e.message}", :error)
160
+ end
161
+
162
+ def check_time
163
+ @check_time ||= Time.now.utc
164
+ end
189
165
  end
190
166
  end
191
167
  end
192
168
  end
193
- end
194
169
  end
@@ -0,0 +1,92 @@
1
+ require "sources-api-client"
2
+
3
+ module TopologicalInventory
4
+ module Providers
5
+ module Common
6
+ class SourcesApiClient < ::SourcesApiClient::ApiClient
7
+ delegate :update_source, :update_endpoint, :update_application, :to => :api
8
+
9
+ INTERNAL_API_PATH = '//internal/v1.0'.freeze
10
+
11
+ def initialize(identity = nil)
12
+ super(::SourcesApiClient::Configuration.default)
13
+ self.identity = identity
14
+ self.api = init_default_api
15
+ end
16
+
17
+ def init_default_api
18
+ default_headers.merge!(identity) if identity.present?
19
+ ::SourcesApiClient::DefaultApi.new(self)
20
+ end
21
+
22
+ def fetch_default_endpoint(source_id)
23
+ endpoints = api.list_source_endpoints(source_id)&.data || []
24
+ endpoints.find(&:default)
25
+ end
26
+
27
+ def fetch_application(source_id)
28
+ applications = api.list_source_applications(source_id)&.data || []
29
+ applications.first
30
+ end
31
+
32
+ def fetch_authentication(source_id, default_endpoint = nil, authtype = nil)
33
+ endpoint = default_endpoint || fetch_default_endpoint(source_id)
34
+ return if endpoint.nil?
35
+
36
+ endpoint_authentications = api.list_endpoint_authentications(endpoint.id.to_s).data || []
37
+ return if endpoint_authentications.empty?
38
+
39
+ auth_id = if authtype.nil?
40
+ endpoint_authentications.first&.id
41
+ else
42
+ endpoint_authentications.detect { |a| a.authtype = authtype }&.id
43
+ end
44
+ return if auth_id.nil?
45
+
46
+ fetch_authentication_with_password(auth_id)
47
+ end
48
+
49
+ private
50
+
51
+ attr_accessor :identity, :api, :custom_base_path
52
+
53
+ def fetch_authentication_with_password(auth_id)
54
+ on_internal_api do
55
+ local_var_path = "/authentications/#{auth_id}"
56
+
57
+ query_params = "expose_encrypted_attribute[]=password"
58
+
59
+ header_params = {'Accept' => select_header_accept(['application/json'])}
60
+ return_type = 'Authentication'
61
+ data, _, _ = call_api(:GET, local_var_path,
62
+ :header_params => header_params,
63
+ :query_params => query_params,
64
+ :auth_names => ['UserSecurity'],
65
+ :return_type => return_type)
66
+ data
67
+ end
68
+ end
69
+
70
+ def build_request_url(path)
71
+ # Add leading and trailing slashes to path
72
+ path = "/#{path}".gsub(/\/+/, '/')
73
+ URI.encode((custom_base_url || @config.base_url) + path)
74
+ end
75
+
76
+ def custom_base_url
77
+ return nil if custom_base_path.nil?
78
+
79
+ url = "#{@config.scheme}://#{[@config.host, custom_base_path].join('/').gsub(/\/+/, '/')}".sub(/\/+\z/, '')
80
+ URI.encode(url)
81
+ end
82
+
83
+ def on_internal_api
84
+ self.custom_base_path = INTERNAL_API_PATH
85
+ yield
86
+ ensure
87
+ self.custom_base_path = nil
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,43 @@
1
+ require "topological_inventory-api-client"
2
+
3
+ module TopologicalInventory
4
+ module Providers
5
+ module Common
6
+ class TopologyApiClient < ::TopologicalInventoryApiClient::ApiClient
7
+ attr_accessor :api
8
+
9
+ def initialize(identity = nil)
10
+ super(::TopologicalInventoryApiClient::Configuration.default)
11
+
12
+ self.identity = identity
13
+ self.api = init_default_api
14
+ end
15
+
16
+ def init_default_api
17
+ default_headers.merge!(identity) if identity.present?
18
+ ::TopologicalInventoryApiClient::DefaultApi.new(self)
19
+ end
20
+
21
+ def update_task(task_id, source_id: nil, state:, status:, target_type: nil, target_source_ref: nil, context: nil)
22
+ params = {'state' => state,
23
+ 'status' => status}
24
+ params['context'] = context if context
25
+ params['source_id'] = source_id if source_id
26
+ params['target_type'] = target_type if target_type
27
+ params['target_source_ref'] = target_source_ref if target_source_ref
28
+ task = TopologicalInventoryApiClient::Task.new(params)
29
+ api.update_task(task_id, task)
30
+ end
31
+
32
+ def svc_instance_url(service_instance)
33
+ rest_api_path = '/service_instances/{id}'.sub('{' + 'id' + '}', service_instance&.id.to_s)
34
+ build_request(:GET, rest_api_path).url
35
+ end
36
+
37
+ private
38
+
39
+ attr_accessor :identity
40
+ end
41
+ end
42
+ end
43
+ end
@@ -1,7 +1,7 @@
1
1
  module TopologicalInventory
2
2
  module Providers
3
3
  module Common
4
- VERSION = "1.0.12"
4
+ VERSION = "2.0.0".freeze
5
5
  end
6
6
  end
7
7
  end
@@ -94,7 +94,7 @@ RSpec.shared_examples "availability_check" do
94
94
  stub_patch(:source, source_patch_body)
95
95
 
96
96
  # Check
97
- api_client = subject.send(:api_client)
97
+ api_client = subject.send(:sources_api)
98
98
  expect(api_client).not_to receive(:update_endpoint)
99
99
 
100
100
  subject.availability_check
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: topological_inventory-providers-common
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.12
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Martin Slemr
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-10-05 00:00:00.000000000 Z
11
+ date: 2020-10-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -252,15 +252,16 @@ files:
252
252
  - lib/topological_inventory/providers/common/collector/parser.rb
253
253
  - lib/topological_inventory/providers/common/collectors_pool.rb
254
254
  - lib/topological_inventory/providers/common/logging.rb
255
+ - lib/topological_inventory/providers/common/mixins/sources_api.rb
256
+ - lib/topological_inventory/providers/common/mixins/topology_api.rb
257
+ - lib/topological_inventory/providers/common/mixins/x_rh_headers.rb
255
258
  - lib/topological_inventory/providers/common/operations/async_worker.rb
256
- - lib/topological_inventory/providers/common/operations/endpoint_client.rb
257
259
  - lib/topological_inventory/providers/common/operations/health_check.rb
258
- - lib/topological_inventory/providers/common/operations/processor.rb
259
260
  - lib/topological_inventory/providers/common/operations/source.rb
260
- - lib/topological_inventory/providers/common/operations/sources_api_client.rb
261
- - lib/topological_inventory/providers/common/operations/topology_api_client.rb
262
261
  - lib/topological_inventory/providers/common/save_inventory/exception.rb
263
262
  - lib/topological_inventory/providers/common/save_inventory/saver.rb
263
+ - lib/topological_inventory/providers/common/sources_api_client.rb
264
+ - lib/topological_inventory/providers/common/topology_api_client.rb
264
265
  - lib/topological_inventory/providers/common/version.rb
265
266
  - spec/spec_helper.rb
266
267
  - spec/support/inventory_helper.rb
@@ -271,7 +272,6 @@ files:
271
272
  - spec/topological_inventory/providers/common/collectors_pool_spec.rb
272
273
  - spec/topological_inventory/providers/common/logger_spec.rb
273
274
  - spec/topological_inventory/providers/common/operations/async_worker_spec.rb
274
- - spec/topological_inventory/providers/common/operations/processor_spec.rb
275
275
  - spec/topological_inventory/providers/common/operations/source_spec.rb
276
276
  - spec/topological_inventory/providers/common/save_inventory/saver_spec.rb
277
277
  - spec/topological_inventory/providers/common_spec.rb
@@ -1,65 +0,0 @@
1
- require "topological_inventory/providers/common/operations/topology_api_client"
2
- require "topological_inventory/providers/common/operations/sources_api_client"
3
-
4
- module TopologicalInventory
5
- module Providers
6
- module Common
7
- module Operations
8
- class EndpointClient
9
- include TopologyApiClient
10
-
11
- def initialize(source_id, task_id, identity = nil)
12
- self.identity = identity
13
- self.source_id = source_id
14
- self.task_id = task_id
15
- end
16
-
17
- def order_service(service_offering, service_plan, order_params)
18
- raise NotImplementedError, "#{__method__} must be implemented in a subclass"
19
- end
20
-
21
- def source_ref_of(endpoint_svc_instance)
22
- raise NotImplementedError, "#{__method__} must be implemented in a subclass"
23
- end
24
-
25
- def wait_for_provision_complete(source_id, endpoint_svc_instance, context = {})
26
- raise NotImplementedError, "#{__method__} must be implemented in a subclass"
27
- end
28
-
29
- def provisioned_successfully?(endpoint_svc_instance)
30
- raise NotImplementedError, "#{__method__} must be implemented in a subclass"
31
- end
32
-
33
- # Endpoint for conversion of provisioned service's status to
34
- # TopologicalInventory Task's status
35
- def task_status_for(endpoint_svc_instance)
36
- raise NotImplementedError, "#{__method__} must be implemented in a subclass"
37
- end
38
-
39
- private
40
-
41
- attr_accessor :identity, :task_id, :source_id
42
-
43
- def sources_api
44
- @sources_api ||= SourcesApiClient.new(identity)
45
- end
46
-
47
- def default_endpoint
48
- @default_endpoint ||= sources_api.fetch_default_endpoint(source_id)
49
- raise "Sources API: Endpoint not found! (source id: #{source_id})" if @default_endpoint.nil?
50
-
51
- @default_endpoint
52
- end
53
-
54
- def authentication
55
- @authentication ||= sources_api.fetch_authentication(source_id, default_endpoint)
56
- end
57
-
58
- def verify_ssl_mode
59
- default_endpoint.verify_ssl ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE
60
- end
61
- end
62
- end
63
- end
64
- end
65
- end
@@ -1,135 +0,0 @@
1
- require "topological_inventory/providers/common/operations/topology_api_client"
2
-
3
- module TopologicalInventory
4
- module Providers
5
- module Common
6
- module Operations
7
- class Processor
8
- include Logging
9
- include TopologyApiClient
10
-
11
- SLEEP_POLL = 10
12
- POLL_TIMEOUT = 1800
13
-
14
- def self.process!(message)
15
- model, method = message.headers['message_type'].to_s.split(".")
16
- new(model, method, message.payload).process
17
- end
18
-
19
- # @param payload [Hash] https://github.com/ManageIQ/topological_inventory-api/blob/master/app/controllers/api/v0/service_plans_controller.rb#L32-L41
20
- def initialize(model, method, payload, metrics = nil)
21
- self.model = model
22
- self.method = method
23
- self.params = payload["params"]
24
- self.identity = payload["request_context"]
25
- self.metrics = metrics
26
- end
27
-
28
- def process
29
- logger.info("Processing #{model}##{method} [#{params}]...")
30
- result = order_service(params)
31
- logger.info("Processing #{model}##{method} [#{params}]...Complete")
32
-
33
- result
34
- end
35
-
36
- private
37
-
38
- attr_accessor :identity, :model, :method, :metrics, :params
39
-
40
- def endpoint_client(source_id, task_id, identity)
41
- raise NotImplementedError, "#{__method__} must be implemented in a subclass as kind of TopologicalInventory::Providers::Common::EndpointClient class"
42
- end
43
-
44
- def order_service(params)
45
- task_id, service_offering_id, service_plan_id, order_params = params.values_at("task_id", "service_offering_id", "service_plan_id", "order_params")
46
-
47
- service_plan = topology_api_client.show_service_plan(service_plan_id) if service_plan_id.present?
48
- service_offering_id = service_plan.service_offering_id if service_offering_id.nil? && service_plan.present?
49
- service_offering = topology_api_client.show_service_offering(service_offering_id)
50
-
51
- source_id = service_offering.source_id
52
- client = endpoint_client(source_id, task_id, identity)
53
-
54
- logger.info("Ordering #{service_offering.name}...")
55
- remote_service_instance = client.order_service(service_offering, service_plan.presence, order_params)
56
- logger.info("Ordering #{service_offering.name}...Complete")
57
-
58
- poll_order_complete_thread(task_id, source_id, remote_service_instance)
59
- rescue StandardError => err
60
- metrics&.record_error
61
- logger.error("[Task #{task_id}] Ordering error: #{err}\n#{err.backtrace.join("\n")}")
62
- update_task(task_id, :state => "completed", :status => "error", :context => {:error => err.to_s})
63
- end
64
-
65
- def poll_order_complete_thread(task_id, source_id, remote_svc_instance)
66
- Thread.new do
67
- begin
68
- poll_order_complete(task_id, source_id, remote_svc_instance)
69
- rescue StandardError => err
70
- logger.error("[Task #{task_id}] Waiting for complete: #{err}\n#{err.backtrace.join("\n")}")
71
- update_task(task_id, :state => "completed", :status => "warn", :context => {:error => err.to_s})
72
- end
73
- end
74
- end
75
-
76
- def poll_order_complete(task_id, source_id, remote_svc_instance)
77
- client = endpoint_client(source_id, task_id, identity)
78
-
79
- context = {
80
- :service_instance => {
81
- :source_id => source_id,
82
- :source_ref => client.source_ref_of(remote_svc_instance)
83
- }
84
- }
85
-
86
- remote_svc_instance = client.wait_for_provision_complete(task_id, remote_svc_instance, context)
87
-
88
- if client.provisioned_successfully?(remote_svc_instance)
89
- if (service_instance = load_topological_svc_instance(source_id, client.source_ref_of(remote_svc_instance))).present?
90
- context[:service_instance][:id] = service_instance.id
91
- context[:service_instance][:url] = svc_instance_url(service_instance)
92
- else
93
- logger.warn("Failed to get service_instance API URL (endpoint's service instance: #{remote_svc_instance.inspect})")
94
- end
95
- end
96
- update_task(task_id, :state => "completed", :status => client.task_status_for(remote_svc_instance), :context => context)
97
- end
98
-
99
- def load_topological_svc_instance(source_id, source_ref)
100
- api = topology_api_client.api_client
101
-
102
- count = 0
103
- timeout_count = POLL_TIMEOUT / SLEEP_POLL
104
-
105
- header_params = { 'Accept' => api.select_header_accept(['application/json']) }
106
- query_params = { :'source_id' => source_id, :'source_ref' => source_ref }
107
- return_type = 'ServiceInstancesCollection'
108
-
109
- service_instance = nil
110
- loop do
111
- data, _status_code, _headers = api.call_api(:GET, "/service_instances",
112
- :header_params => header_params,
113
- :query_params => query_params,
114
- :auth_names => ['UserSecurity'],
115
- :return_type => return_type)
116
-
117
- service_instance = data.data&.first if data.meta.count > 0
118
- break if service_instance.present?
119
-
120
- break if (count += 1) >= timeout_count
121
-
122
- sleep(SLEEP_POLL) # seconds
123
- end
124
-
125
- if service_instance.nil?
126
- logger.error("Failed to find service_instance by source_id [#{source_id}] source_ref [#{source_ref}]")
127
- end
128
-
129
- service_instance
130
- end
131
- end
132
- end
133
- end
134
- end
135
- end
@@ -1,94 +0,0 @@
1
- require "sources-api-client"
2
-
3
- module TopologicalInventory
4
- module Providers
5
- module Common
6
- module Operations
7
- class SourcesApiClient < ::SourcesApiClient::ApiClient
8
- delegate :update_source, :update_endpoint, :update_application, :to => :api
9
-
10
- INTERNAL_API_PATH = '//internal/v1.0'.freeze
11
-
12
- def initialize(identity = nil)
13
- super(::SourcesApiClient::Configuration.default)
14
- self.identity = identity
15
- self.api = init_default_api
16
- end
17
-
18
- def init_default_api
19
- default_headers.merge!(identity) if identity.present?
20
- ::SourcesApiClient::DefaultApi.new(self)
21
- end
22
-
23
- def fetch_default_endpoint(source_id)
24
- endpoints = api.list_source_endpoints(source_id)&.data || []
25
- endpoints.find(&:default)
26
- end
27
-
28
- def fetch_application(source_id)
29
- applications = api.list_source_applications(source_id)&.data || []
30
- applications.first
31
- end
32
-
33
- def fetch_authentication(source_id, default_endpoint = nil, authtype = nil)
34
- endpoint = default_endpoint || fetch_default_endpoint(source_id)
35
- return if endpoint.nil?
36
-
37
- endpoint_authentications = api.list_endpoint_authentications(endpoint.id.to_s).data || []
38
- return if endpoint_authentications.empty?
39
-
40
- auth_id = if authtype.nil?
41
- endpoint_authentications.first&.id
42
- else
43
- endpoint_authentications.detect { |a| a.authtype = authtype }&.id
44
- end
45
- return if auth_id.nil?
46
-
47
- fetch_authentication_with_password(auth_id)
48
- end
49
-
50
- private
51
-
52
- attr_accessor :identity, :api, :custom_base_path
53
-
54
- def fetch_authentication_with_password(auth_id)
55
- on_internal_api do
56
- local_var_path = "/authentications/#{auth_id}"
57
-
58
- query_params = "expose_encrypted_attribute[]=password"
59
-
60
- header_params = { 'Accept' => select_header_accept(['application/json']) }
61
- return_type = 'Authentication'
62
- data, _, _ = call_api(:GET, local_var_path,
63
- :header_params => header_params,
64
- :query_params => query_params,
65
- :auth_names => ['UserSecurity'],
66
- :return_type => return_type)
67
- data
68
- end
69
- end
70
-
71
- def build_request_url(path)
72
- # Add leading and trailing slashes to path
73
- path = "/#{path}".gsub(/\/+/, '/')
74
- URI.encode((custom_base_url || @config.base_url) + path)
75
- end
76
-
77
- def custom_base_url
78
- return nil if custom_base_path.nil?
79
-
80
- url = "#{@config.scheme}://#{[@config.host, custom_base_path].join('/').gsub(/\/+/, '/')}".sub(/\/+\z/, '')
81
- URI.encode(url)
82
- end
83
-
84
- def on_internal_api
85
- self.custom_base_path = INTERNAL_API_PATH
86
- yield
87
- ensure
88
- self.custom_base_path = nil
89
- end
90
- end
91
- end
92
- end
93
- end
94
- end
@@ -1,28 +0,0 @@
1
- module TopologicalInventory
2
- module Providers
3
- module Common
4
- module Operations
5
- module TopologyApiClient
6
- def topology_api_client
7
- @topology_api_client ||=
8
- begin
9
- api_client = TopologicalInventoryApiClient::ApiClient.new
10
- api_client.default_headers.merge!(identity) if identity.present?
11
- TopologicalInventoryApiClient::DefaultApi.new(api_client)
12
- end
13
- end
14
-
15
- def update_task(task_id, state:, status:, context:)
16
- task = TopologicalInventoryApiClient::Task.new("state" => state, "status" => status, "context" => context)
17
- topology_api_client.update_task(task_id, task)
18
- end
19
-
20
- def svc_instance_url(service_instance)
21
- rest_api_path = '/service_instances/{id}'.sub('{' + 'id' + '}', service_instance&.id.to_s)
22
- topology_api_client.api_client.build_request(:GET, rest_api_path).url
23
- end
24
- end
25
- end
26
- end
27
- end
28
- end
@@ -1,102 +0,0 @@
1
- require "topological_inventory/providers/common/operations/processor"
2
-
3
- RSpec.describe TopologicalInventory::Providers::Common::Operations::Processor do
4
- let(:topology_api_client) { double }
5
- let(:source_id) { 1 }
6
- let(:source_ref) { 1000 }
7
- let(:service_plan) { double("TopologicalInventoryApiClient::ServicePlan") }
8
- let(:service_offering) { double("TopologicalInventoryApiClient::ServiceOffering") }
9
-
10
- # Overriden in contexts
11
- let(:payload) { {} }
12
-
13
- before do
14
- @processor = described_class.new(nil, nil, payload)
15
- allow(@processor).to receive(:logger).and_return(double('null_object').as_null_object)
16
-
17
- allow(service_plan).to receive(:service_offering_id).and_return(1)
18
- allow(service_plan).to receive(:name).and_return(double)
19
-
20
- allow(service_offering).to receive(:name).and_return(double)
21
- allow(service_offering).to receive(:source_ref).and_return(source_ref)
22
- allow(service_offering).to receive(:extra).and_return({:type => 'job_template'})
23
- allow(service_offering).to receive(:source_id).and_return(source_id)
24
-
25
- @endpoint_client = double
26
- allow(@endpoint_client).to receive(:order_service)
27
-
28
- allow(@processor).to receive(:endpoint_client).and_return(@endpoint_client)
29
- allow(@processor).to receive(:topology_api_client).and_return(topology_api_client)
30
- allow(topology_api_client).to receive(:update_task)
31
- allow(topology_api_client).to receive(:show_service_plan).and_return(service_plan)
32
- allow(topology_api_client).to receive(:show_service_offering).and_return(service_offering)
33
- end
34
-
35
- context "Order by ServicePlan" do
36
- let(:payload) do
37
- {
38
- 'request_context' => {"x-rh-identity" => 'abcd'},
39
- 'params' => {
40
- 'order_params' => {
41
- 'service_plan_id' => 1,
42
- 'service_parameters' => { :name => "Job 1",
43
- :param1 => "Test Topology",
44
- :param2 => 50 },
45
- 'provider_control_parameters' => {}
46
- },
47
- 'service_plan_id' => 1,
48
- 'task_id' => 1 # in tp-inv api (Task)
49
- }
50
- }
51
- end
52
-
53
- describe "#order_service" do
54
- it "orders job" do
55
- allow(@processor).to receive(:poll_order_complete_thread).and_return(double)
56
-
57
- expect(@endpoint_client).to receive(:order_service).with(service_offering, service_plan, payload['params']['order_params'])
58
- @processor.send(:order_service, payload['params'])
59
- end
60
-
61
- it "updates task on error" do
62
- err_message = "Sample error"
63
-
64
- allow(@processor).to receive(:poll_order_complete_thread).and_return(double)
65
- allow(@processor).to receive(:update_task).and_return(double)
66
- allow(@endpoint_client).to receive(:order_service).and_raise(err_message)
67
-
68
- expect(@processor).to receive(:update_task).with(payload['params']['task_id'], :state => "completed", :status => "error", :context => { :error => err_message })
69
-
70
- @processor.send(:order_service, payload['params'])
71
- end
72
- end
73
- end
74
-
75
- context "Order by ServiceOffering" do
76
- let(:payload) do
77
- {
78
- 'request_context' => {"x-rh-identity" => 'abcd'},
79
- 'params' => {
80
- 'order_params' => {
81
- 'service_offering_id' => 1,
82
- 'service_parameters' => { :name => "Job 1",
83
- :param1 => "Test Topology",
84
- :param2 => 50 },
85
- 'provider_control_parameters' => {}
86
- },
87
- 'service_offering_id' => 1,
88
- 'task_id' => 1 # in tp-inv api (Task)
89
- }
90
- }
91
- end
92
-
93
- describe "#order_service" do
94
- it "orders job" do
95
- allow(@processor).to receive(:poll_order_complete_thread).and_return(double)
96
-
97
- expect(@endpoint_client).to receive(:order_service).with(service_offering, nil, payload['params']['order_params'])
98
- @processor.send(:order_service, payload['params'])
99
- end
100
- end
101
- end
102
- end