topological_inventory-providers-common 1.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 +7 -0
- data/.gitignore +13 -0
- data/.rspec +3 -0
- data/.travis.yml +10 -0
- data/Gemfile +15 -0
- data/LICENSE.txt +21 -0
- data/README.md +3 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/topological_inventory/providers/common.rb +15 -0
- data/lib/topological_inventory/providers/common/collector.rb +174 -0
- data/lib/topological_inventory/providers/common/collector/inventory_collection_storage.rb +69 -0
- data/lib/topological_inventory/providers/common/collector/inventory_collection_wrapper.rb +25 -0
- data/lib/topological_inventory/providers/common/collector/parser.rb +34 -0
- data/lib/topological_inventory/providers/common/collectors_pool.rb +135 -0
- data/lib/topological_inventory/providers/common/logging.rb +22 -0
- data/lib/topological_inventory/providers/common/operations/endpoint_client.rb +62 -0
- data/lib/topological_inventory/providers/common/operations/processor.rb +135 -0
- data/lib/topological_inventory/providers/common/operations/sources_api_client.rb +85 -0
- data/lib/topological_inventory/providers/common/operations/topology_api_client.rb +28 -0
- data/lib/topological_inventory/providers/common/save_inventory/exception.rb +14 -0
- data/lib/topological_inventory/providers/common/save_inventory/saver.rb +124 -0
- data/lib/topological_inventory/providers/common/version.rb +7 -0
- data/topological_inventory-providers-common.gemspec +35 -0
- metadata +203 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 1285f00b03ef283aef07ea0cb117a765666b57c8debf8d5b4c058669dcb1b6f5
|
4
|
+
data.tar.gz: 9474bfdf4847a394cbc7ee25dfd8624c5b7c852fe982525f53d06207cb88aac4
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 70fef12804eac71773b0dd4f3adfc15044ec027ca11c4a319f4c2d46427c2d605c34d2751bf38bdc69759f7a0dc1a37b9311ad3044c64721e9919bdbea5a5a6a
|
7
|
+
data.tar.gz: eed534a98d8424083af1d7a3964c404656e977a08094d4c574b75407c91ef0d7b4a4712f102ea0c7475bc0382f06d965f04c81a2cc1a141d1cb1bec0d75f397e
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
source "https://rubygems.org"
|
2
|
+
|
3
|
+
plugin 'bundler-inject', '~> 1.1'
|
4
|
+
require File.join(Bundler::Plugin.index.load_paths("bundler-inject")[0], "bundler-inject") rescue nil
|
5
|
+
|
6
|
+
# Specify your gem's dependencies in topological_inventory-providers-common.gemspec
|
7
|
+
gemspec
|
8
|
+
|
9
|
+
gem "sources-api-client", "~> 1.0"
|
10
|
+
gem "topological_inventory-ingress_api-client", "~> 1.0"
|
11
|
+
|
12
|
+
group :development, :test do
|
13
|
+
gem 'rake', '~> 12.0.0'
|
14
|
+
gem 'pry-byebug'
|
15
|
+
end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2019 Martin Slemr
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "topological_inventory/providers/common"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require "topological_inventory/providers/common/version"
|
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
|
+
require "topological_inventory/providers/common/collectors_pool"
|
6
|
+
require "topological_inventory/providers/common/collector"
|
7
|
+
|
8
|
+
module TopologicalInventory
|
9
|
+
module Providers
|
10
|
+
module Common
|
11
|
+
class Error < StandardError; end
|
12
|
+
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,174 @@
|
|
1
|
+
require "active_support/inflector"
|
2
|
+
require "concurrent"
|
3
|
+
require "topological_inventory-ingress_api-client"
|
4
|
+
require "topological_inventory/providers/common/collector/inventory_collection_storage"
|
5
|
+
require "topological_inventory/providers/common/collector/inventory_collection_wrapper"
|
6
|
+
require "topological_inventory/providers/common/collector/parser"
|
7
|
+
require "topological_inventory/providers/common/save_inventory/saver"
|
8
|
+
|
9
|
+
module TopologicalInventory
|
10
|
+
module Providers
|
11
|
+
module Common
|
12
|
+
class Collector
|
13
|
+
# @param poll_time [Integer] Waiting between collecting loops. Irrelevant for standalone_mode: true
|
14
|
+
# @param standalone_mode [Boolean] T/F if collector is created by collectors_pool
|
15
|
+
def initialize(source, default_limit: 1_000, poll_time: 30, standalone_mode: true)
|
16
|
+
self.collector_threads = Concurrent::Map.new
|
17
|
+
self.finished = Concurrent::AtomicBoolean.new(false)
|
18
|
+
self.poll_time = poll_time
|
19
|
+
self.limits = Hash.new(default_limit)
|
20
|
+
self.queue = Queue.new
|
21
|
+
self.source = source
|
22
|
+
self.standalone_mode = standalone_mode
|
23
|
+
end
|
24
|
+
|
25
|
+
def collect!
|
26
|
+
start_collector_threads
|
27
|
+
|
28
|
+
until finished? do
|
29
|
+
ensure_collector_threads
|
30
|
+
|
31
|
+
notices = []
|
32
|
+
notices << queue.pop until queue.empty?
|
33
|
+
|
34
|
+
targeted_refresh(notices) unless notices.empty?
|
35
|
+
|
36
|
+
standalone_mode ? sleep(poll_time) : stop
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def stop
|
41
|
+
finished.value = true
|
42
|
+
end
|
43
|
+
|
44
|
+
protected
|
45
|
+
|
46
|
+
attr_accessor :collector_threads, :finished, :limits,
|
47
|
+
:poll_time, :queue, :source, :standalone_mode
|
48
|
+
|
49
|
+
def finished?
|
50
|
+
finished.value
|
51
|
+
end
|
52
|
+
|
53
|
+
def entity_types
|
54
|
+
endpoint_types.flat_map { |endpoint| send("#{endpoint}_entity_types") }
|
55
|
+
end
|
56
|
+
|
57
|
+
# Should be overriden by subclass
|
58
|
+
# Entity types collected from endpoints
|
59
|
+
def endpoint_types
|
60
|
+
%w()
|
61
|
+
end
|
62
|
+
|
63
|
+
def start_collector_threads
|
64
|
+
entity_types.each do |entity_type|
|
65
|
+
next if collector_threads[entity_type]&.alive?
|
66
|
+
|
67
|
+
collector_threads[entity_type] = start_collector_thread(entity_type)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def ensure_collector_threads
|
72
|
+
start_collector_threads
|
73
|
+
end
|
74
|
+
|
75
|
+
def start_collector_thread(entity_type)
|
76
|
+
connection = connection_for_entity_type(entity_type)
|
77
|
+
return if connection.nil?
|
78
|
+
|
79
|
+
Thread.new do
|
80
|
+
collector_thread(connection, entity_type)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# Connection to endpoint for each entity type
|
85
|
+
def connection_for_entity_type(_entity_type)
|
86
|
+
raise NotImplementedError
|
87
|
+
end
|
88
|
+
|
89
|
+
# Thread's main for collecting one entity type's data
|
90
|
+
def collector_thread(_connection, _entity_type)
|
91
|
+
raise NotImplementedError
|
92
|
+
end
|
93
|
+
|
94
|
+
# @optional
|
95
|
+
# Listen to notices from threads
|
96
|
+
def targeted_refresh(notices)
|
97
|
+
end
|
98
|
+
|
99
|
+
# @param refresh_state_part_collected_at [Time] when this payload is collected (for [Core]:RefreshStatePart)
|
100
|
+
# @param refresh_state_part_sent_at [Time] when this payload is sent (for [Core]:RefreshStatePart)
|
101
|
+
def save_inventory(collections,
|
102
|
+
inventory_name,
|
103
|
+
schema,
|
104
|
+
refresh_state_uuid = nil,
|
105
|
+
refresh_state_part_uuid = nil,
|
106
|
+
refresh_state_part_collected_at = nil,
|
107
|
+
refresh_state_part_sent_at = Time.now.utc)
|
108
|
+
return 0 if collections.empty?
|
109
|
+
|
110
|
+
SaveInventory::Saver.new(:client => ingress_api_client, :logger => logger).save(
|
111
|
+
:inventory => TopologicalInventoryIngressApiClient::Inventory.new(
|
112
|
+
:name => inventory_name,
|
113
|
+
:schema => TopologicalInventoryIngressApiClient::Schema.new(:name => schema),
|
114
|
+
:source => source,
|
115
|
+
:collections => collections,
|
116
|
+
:refresh_state_uuid => refresh_state_uuid,
|
117
|
+
:refresh_state_part_uuid => refresh_state_part_uuid,
|
118
|
+
:refresh_state_part_collected_at => refresh_state_part_collected_at,
|
119
|
+
:refresh_state_part_sent_at => refresh_state_part_sent_at
|
120
|
+
)
|
121
|
+
)
|
122
|
+
rescue => e
|
123
|
+
response_body = e.response_body if e.respond_to? :response_body
|
124
|
+
response_headers = e.response_headers if e.respond_to? :response_headers
|
125
|
+
logger.error("Error when sending payload to Ingress API. Error message: #{e.message}. Body: #{response_body}. Header: #{response_headers}")
|
126
|
+
raise e
|
127
|
+
end
|
128
|
+
|
129
|
+
# @param refresh_state_started_at [Time] when collecting of this entity type is started (for [Core]:RefreshState)
|
130
|
+
# @param refresh_state_sent_at [Time] when this payload is sent (for [Core]:RefreshState)
|
131
|
+
def sweep_inventory(inventory_name,
|
132
|
+
schema,
|
133
|
+
refresh_state_uuid,
|
134
|
+
total_parts,
|
135
|
+
sweep_scope,
|
136
|
+
refresh_state_started_at = nil,
|
137
|
+
refresh_state_sent_at = Time.now.utc)
|
138
|
+
return if !total_parts || sweep_scope.empty?
|
139
|
+
|
140
|
+
SaveInventory::Saver.new(:client => ingress_api_client, :logger => logger).save(
|
141
|
+
:inventory => TopologicalInventoryIngressApiClient::Inventory.new(
|
142
|
+
:name => inventory_name,
|
143
|
+
:schema => TopologicalInventoryIngressApiClient::Schema.new(:name => schema),
|
144
|
+
:source => source,
|
145
|
+
:collections => [],
|
146
|
+
:refresh_state_uuid => refresh_state_uuid,
|
147
|
+
:total_parts => total_parts,
|
148
|
+
:sweep_scope => sweep_scope,
|
149
|
+
:refresh_state_started_at => refresh_state_started_at,
|
150
|
+
:refresh_state_sent_at => refresh_state_sent_at
|
151
|
+
)
|
152
|
+
)
|
153
|
+
rescue => e
|
154
|
+
response_body = e.response_body if e.respond_to? :response_body
|
155
|
+
response_headers = e.response_headers if e.respond_to? :response_headers
|
156
|
+
logger.error("Error when sending payload to Ingress API. Error message: #{e.message}. Body: #{response_body}. Header: #{response_headers}")
|
157
|
+
raise e
|
158
|
+
end
|
159
|
+
|
160
|
+
def inventory_name
|
161
|
+
"Default"
|
162
|
+
end
|
163
|
+
|
164
|
+
def schema_name
|
165
|
+
"Default"
|
166
|
+
end
|
167
|
+
|
168
|
+
def ingress_api_client
|
169
|
+
TopologicalInventoryIngressApiClient::DefaultApi.new
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module TopologicalInventory
|
2
|
+
module Providers
|
3
|
+
module Common
|
4
|
+
class Collector
|
5
|
+
class InventoryCollectionStorage
|
6
|
+
attr_accessor :data
|
7
|
+
|
8
|
+
delegate :values, :to => :data
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@data = {}
|
12
|
+
end
|
13
|
+
|
14
|
+
def add_collection(name, overwrite: true)
|
15
|
+
return @data[name] if !@data[name].nil? && !overwrite
|
16
|
+
|
17
|
+
if ingress_api_model_exists?(name)
|
18
|
+
@data[name] ||= InventoryCollectionWrapper.new(:name => name)
|
19
|
+
else
|
20
|
+
raise NameError, "TopologicalInventoryIngressApiClient::#{name.to_s.classify} doesn't exist"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Creates collection automatically
|
25
|
+
def [](name)
|
26
|
+
add_collection(name, :overwrite => false)
|
27
|
+
end
|
28
|
+
|
29
|
+
# @return [Array<Symbol>] array of InventoryCollection object names of the persister
|
30
|
+
def inventory_collections_names
|
31
|
+
@data.keys
|
32
|
+
end
|
33
|
+
|
34
|
+
def method_missing(method_name, *arguments, &block)
|
35
|
+
add_collection(method_name, :overwrite => false) # init collection if not exist
|
36
|
+
|
37
|
+
if inventory_collections_names.include?(method_name)
|
38
|
+
self.class.define_collections_reader(method_name)
|
39
|
+
send(method_name)
|
40
|
+
else
|
41
|
+
super
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# @return [Boolean] true if InventoryCollection with passed method_name name is defined
|
46
|
+
def respond_to_missing?(method_name, _include_private = false)
|
47
|
+
ingress_api_model_exists?(method_name) || super
|
48
|
+
end
|
49
|
+
|
50
|
+
protected
|
51
|
+
|
52
|
+
def ingress_api_model_exists?(method_name)
|
53
|
+
class_name = "TopologicalInventoryIngressApiClient::#{method_name.to_s.classify}"
|
54
|
+
|
55
|
+
# nil test is not enough due to sometimes weird namespace autoloading
|
56
|
+
class_name.safe_constantize.to_s == class_name
|
57
|
+
end
|
58
|
+
|
59
|
+
# Defines a new attr reader returning InventoryCollection object
|
60
|
+
def self.define_collections_reader(collection_key)
|
61
|
+
define_method(collection_key) do
|
62
|
+
add_collection(collection_key, :overwrite => false)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module TopologicalInventory
|
2
|
+
module Providers
|
3
|
+
module Common
|
4
|
+
class Collector
|
5
|
+
class InventoryCollectionWrapper < TopologicalInventoryIngressApiClient::InventoryCollection
|
6
|
+
def initialize(name:)
|
7
|
+
super(:name => name, :data => [])
|
8
|
+
end
|
9
|
+
|
10
|
+
def build(properties)
|
11
|
+
obj = get_model.new(properties)
|
12
|
+
data << obj
|
13
|
+
obj
|
14
|
+
end
|
15
|
+
|
16
|
+
protected
|
17
|
+
|
18
|
+
def get_model
|
19
|
+
"TopologicalInventoryIngressApiClient::#{name.to_s.classify}".constantize
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require "active_support/inflector"
|
2
|
+
|
3
|
+
module TopologicalInventory
|
4
|
+
module Providers
|
5
|
+
module Common
|
6
|
+
class Collector
|
7
|
+
class Parser
|
8
|
+
attr_accessor :collections, :resource_timestamp
|
9
|
+
|
10
|
+
delegate :add_collection, :to => :collections
|
11
|
+
|
12
|
+
def initialize
|
13
|
+
@collections = InventoryCollectionStorage.new
|
14
|
+
|
15
|
+
self.resource_timestamp = Time.now.utc
|
16
|
+
end
|
17
|
+
|
18
|
+
def lazy_find(collection, reference, ref: :manager_ref)
|
19
|
+
return if reference.kind_of?(String) && reference.blank?
|
20
|
+
|
21
|
+
# Don't make lazy link if all reference values are blank
|
22
|
+
return if reference.kind_of?(Hash) && reference.values.select { |val| val.to_s.present? }.blank?
|
23
|
+
|
24
|
+
TopologicalInventoryIngressApiClient::InventoryObjectLazy.new(
|
25
|
+
:inventory_collection_name => collection,
|
26
|
+
:reference => reference,
|
27
|
+
:ref => ref,
|
28
|
+
)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
require "config"
|
2
|
+
|
3
|
+
module TopologicalInventory
|
4
|
+
module Providers
|
5
|
+
module Common
|
6
|
+
class CollectorsPool
|
7
|
+
SECRET_FILENAME = "credentials".freeze
|
8
|
+
|
9
|
+
def initialize(config_name, metrics, collector_poll_time: 60, thread_pool_size: 2)
|
10
|
+
self.config_name = config_name
|
11
|
+
self.collector_status = Concurrent::Map.new
|
12
|
+
self.metrics = metrics
|
13
|
+
self.collector_poll_time = collector_poll_time
|
14
|
+
self.secrets = nil
|
15
|
+
self.thread_pool = Concurrent::FixedThreadPool.new(thread_pool_size)
|
16
|
+
self.updated_at = {}
|
17
|
+
end
|
18
|
+
|
19
|
+
def run!
|
20
|
+
loop do
|
21
|
+
reload_config
|
22
|
+
reload_secrets
|
23
|
+
|
24
|
+
# Secret is deployed just after config map, we should wait for it
|
25
|
+
queue_collectors if secrets_newer_than_config?
|
26
|
+
|
27
|
+
sleep(5)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def stop!
|
32
|
+
collectors.each_value(&:stop)
|
33
|
+
|
34
|
+
thread_pool.shutdown
|
35
|
+
# Wait for end of collectors to ensure metrics are stopped after them
|
36
|
+
thread_pool.wait_for_termination
|
37
|
+
end
|
38
|
+
|
39
|
+
protected
|
40
|
+
|
41
|
+
attr_accessor :collectors, :collector_poll_time, :collector_status, :thread_pool, :config_name,
|
42
|
+
:metrics, :secrets, :updated_at
|
43
|
+
|
44
|
+
def reload_config
|
45
|
+
config_file = File.join(path_to_config, "#{sanitize_filename(config_name)}.yml")
|
46
|
+
raise "Configuration file #{config_file} doesn't exist" unless File.exist?(config_file)
|
47
|
+
|
48
|
+
::Config.load_and_set_settings(config_file)
|
49
|
+
end
|
50
|
+
|
51
|
+
def reload_secrets
|
52
|
+
path = File.join(path_to_secrets, SECRET_FILENAME)
|
53
|
+
raise "Secrets file missing at #{path}" unless File.exists?(path)
|
54
|
+
file = File.read(path)
|
55
|
+
self.secrets = JSON.parse(file)
|
56
|
+
end
|
57
|
+
|
58
|
+
# @param [Hash] source from Settings
|
59
|
+
# @return [Hash|nil] {"username":, "password":}
|
60
|
+
def secrets_for_source(source)
|
61
|
+
secrets[source.source]
|
62
|
+
end
|
63
|
+
|
64
|
+
def queue_collectors
|
65
|
+
::Settings.sources.to_a.each do |source|
|
66
|
+
# Skip if collector is running/queued or just finished
|
67
|
+
next if queued_or_updated_recently?(source)
|
68
|
+
|
69
|
+
# Check if secrets for this source are present
|
70
|
+
next if (source_secret = secrets_for_source(source)).nil?
|
71
|
+
|
72
|
+
# Check if necessary endpoint/auth data are not blank (provider specific)
|
73
|
+
next unless source_valid?(source, source_secret)
|
74
|
+
|
75
|
+
collector_status[source.source] = {:status => :queued}
|
76
|
+
# Add source to collector's queue
|
77
|
+
thread_pool.post do
|
78
|
+
begin
|
79
|
+
collector = new_collector(source, source_secret)
|
80
|
+
collector.collect!
|
81
|
+
ensure
|
82
|
+
collector_status[source.source] = {:status => :ready, :last_updated_at => Time.now}
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def queued_or_updated_recently?(source)
|
89
|
+
return false if collector_status[source.source].nil?
|
90
|
+
return true if collector_status[source.source][:status] == :queued
|
91
|
+
|
92
|
+
if (last_updated_at = collector_status[source.source][:last_updated_at]).nil?
|
93
|
+
# should never happen
|
94
|
+
last_updated_at = Time.now
|
95
|
+
collector_status[source.source] = {:status => :ready, :last_updated_at => last_updated_at}
|
96
|
+
end
|
97
|
+
|
98
|
+
last_updated_at > Time.now - collector_poll_time.to_i
|
99
|
+
end
|
100
|
+
|
101
|
+
def secrets_newer_than_config?
|
102
|
+
return false if ::Settings.updated_at.nil? || secrets["updated_at"].nil?
|
103
|
+
|
104
|
+
updated_at[:config] = Time.parse(::Settings.updated_at)
|
105
|
+
updated_at[:secret] = Time.parse(secrets["updated_at"])
|
106
|
+
|
107
|
+
logger.info("Reloading Sources data => Config [updated_at: #{updated_at[:config].to_s}], Secrets [updated at: #{updated_at[:secret]}]") if updated_at[:config] <= updated_at[:secret]
|
108
|
+
|
109
|
+
updated_at[:config] <= updated_at[:secret]
|
110
|
+
end
|
111
|
+
|
112
|
+
def source_valid?(source, secret)
|
113
|
+
true
|
114
|
+
end
|
115
|
+
|
116
|
+
def path_to_config
|
117
|
+
raise NotImplementedError, "#{__method__} must be implemented in a subclass"
|
118
|
+
end
|
119
|
+
|
120
|
+
def path_to_secrets
|
121
|
+
raise NotImplementedError, "#{__method__} must be implemented in a subclass"
|
122
|
+
end
|
123
|
+
|
124
|
+
def sanitize_filename(filename)
|
125
|
+
# Remove any character that aren't 0-9, A-Z, or a-z, / or -
|
126
|
+
filename.gsub(/[^0-9A-Z\/\-]/i, '_')
|
127
|
+
end
|
128
|
+
|
129
|
+
def new_collector(source, source_secret)
|
130
|
+
raise NotImplementedError, "#{__method__} must be implemented in a subclass"
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require "manageiq/loggers"
|
2
|
+
|
3
|
+
module TopologicalInventory
|
4
|
+
module Providers
|
5
|
+
module Common
|
6
|
+
class << self
|
7
|
+
attr_writer :logger
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.logger
|
11
|
+
@logger ||= ManageIQ::Loggers::Container.new
|
12
|
+
@logger
|
13
|
+
end
|
14
|
+
|
15
|
+
module Logging
|
16
|
+
def logger
|
17
|
+
TopologicalInventory::Providers::Common.logger
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,62 @@
|
|
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
|
+
end
|
50
|
+
|
51
|
+
def authentication
|
52
|
+
@authentication ||= sources_api.fetch_authentication(source_id, default_endpoint)
|
53
|
+
end
|
54
|
+
|
55
|
+
def verify_ssl_mode
|
56
|
+
default_endpoint.verify_ssl ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,135 @@
|
|
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
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require "sources-api-client"
|
2
|
+
|
3
|
+
module TopologicalInventory
|
4
|
+
module Providers
|
5
|
+
module Common
|
6
|
+
module Operations
|
7
|
+
class SourcesApiClient < ::SourcesApiClient::ApiClient
|
8
|
+
INTERNAL_API_PATH = '//internal/v1.0'.freeze
|
9
|
+
|
10
|
+
def initialize(identity = nil)
|
11
|
+
super(::SourcesApiClient::Configuration.default)
|
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
|
+
::SourcesApiClient::DefaultApi.new(self)
|
19
|
+
end
|
20
|
+
|
21
|
+
def fetch_default_endpoint(source_id)
|
22
|
+
endpoints = api.list_source_endpoints(source_id)&.data || []
|
23
|
+
endpoint = endpoints.find(&:default)
|
24
|
+
|
25
|
+
raise "Sources API: Endpoint not found! (source id: #{source_id})" if endpoint.nil?
|
26
|
+
|
27
|
+
endpoint
|
28
|
+
end
|
29
|
+
|
30
|
+
def fetch_authentication(source_id, default_endpoint = nil)
|
31
|
+
endpoint = default_endpoint || fetch_default_endpoint(source_id)
|
32
|
+
return if endpoint.nil?
|
33
|
+
|
34
|
+
endpoint_authentications = api.list_endpoint_authentications(endpoint.id.to_s).data || []
|
35
|
+
return if endpoint_authentications.empty?
|
36
|
+
|
37
|
+
auth_id = endpoint_authentications.first.id
|
38
|
+
fetch_authentication_with_password(auth_id)
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
attr_accessor :identity, :api, :custom_base_path
|
44
|
+
|
45
|
+
def fetch_authentication_with_password(auth_id)
|
46
|
+
on_internal_api do
|
47
|
+
local_var_path = "/authentications/#{auth_id}"
|
48
|
+
|
49
|
+
query_params = "expose_encrypted_attribute[]=password"
|
50
|
+
|
51
|
+
header_params = { 'Accept' => select_header_accept(['application/json']) }
|
52
|
+
return_type = 'Authentication'
|
53
|
+
data, _, _ = call_api(:GET, local_var_path,
|
54
|
+
:header_params => header_params,
|
55
|
+
:query_params => query_params,
|
56
|
+
:auth_names => ['UserSecurity'],
|
57
|
+
:return_type => return_type)
|
58
|
+
data
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def build_request_url(path)
|
63
|
+
# Add leading and trailing slashes to path
|
64
|
+
path = "/#{path}".gsub(/\/+/, '/')
|
65
|
+
URI.encode((custom_base_url || @config.base_url) + path)
|
66
|
+
end
|
67
|
+
|
68
|
+
def custom_base_url
|
69
|
+
return nil if custom_base_path.nil?
|
70
|
+
|
71
|
+
url = "#{@config.scheme}://#{[@config.host, custom_base_path].join('/').gsub(/\/+/, '/')}".sub(/\/+\z/, '')
|
72
|
+
URI.encode(url)
|
73
|
+
end
|
74
|
+
|
75
|
+
def on_internal_api
|
76
|
+
self.custom_base_path = INTERNAL_API_PATH
|
77
|
+
yield
|
78
|
+
ensure
|
79
|
+
self.custom_base_path = nil
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,28 @@
|
|
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
|
@@ -0,0 +1,124 @@
|
|
1
|
+
require "topological_inventory/providers/common/save_inventory/exception"
|
2
|
+
|
3
|
+
module TopologicalInventory
|
4
|
+
module Providers
|
5
|
+
module Common
|
6
|
+
module SaveInventory
|
7
|
+
class Saver
|
8
|
+
# As defined in:
|
9
|
+
# https://github.com/zendesk/ruby-kafka/blob/02f7e2816e1130c5202764c275e36837f57ca4af/lib/kafka/protocol/message.rb#L11-L17
|
10
|
+
# There is at least 112 bytes that are added as a message header, so we need to keep room for that. Lets make
|
11
|
+
# it 200 bytes, just for sure.
|
12
|
+
KAFKA_RESERVED_HEADER_SIZE = 200
|
13
|
+
|
14
|
+
def initialize(client:, logger:, max_bytes: 1_000_000)
|
15
|
+
@client = client
|
16
|
+
@logger = logger
|
17
|
+
@max_bytes = max_bytes - KAFKA_RESERVED_HEADER_SIZE
|
18
|
+
end
|
19
|
+
|
20
|
+
attr_reader :client, :logger, :max_bytes
|
21
|
+
|
22
|
+
# @return [Integer] A total number of parts that the payload was divided into
|
23
|
+
def save(data)
|
24
|
+
inventory = data[:inventory].to_hash
|
25
|
+
|
26
|
+
inventory_json = JSON.generate(inventory)
|
27
|
+
if inventory_json.size < max_bytes
|
28
|
+
save_inventory(inventory_json)
|
29
|
+
return 1
|
30
|
+
else
|
31
|
+
# GC can clean this up
|
32
|
+
inventory_json = nil
|
33
|
+
return save_payload_in_batches(inventory)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def save_payload_in_batches(inventory)
|
40
|
+
parts = 0
|
41
|
+
new_inventory = build_new_inventory(inventory)
|
42
|
+
|
43
|
+
inventory[:collections].each do |collection|
|
44
|
+
new_collection = build_new_collection(collection)
|
45
|
+
|
46
|
+
data = collection[:data].map { |x| JSON.generate(x) }
|
47
|
+
# Lets compute sizes of the each data item, plus 1 byte for comma
|
48
|
+
data_sizes = data.map { |x| x.size + 1 }
|
49
|
+
|
50
|
+
# Size of the current inventory and new_collection wrapper, plus 2 bytes for array signs
|
51
|
+
wrapper_size = JSON.generate(new_inventory).size + JSON.generate(new_collection).size + 2
|
52
|
+
total_size = wrapper_size
|
53
|
+
counter = 0
|
54
|
+
data_sizes.each do |data_size|
|
55
|
+
counter += 1
|
56
|
+
total_size += data_size
|
57
|
+
|
58
|
+
if total_size > max_bytes
|
59
|
+
# Remove the last data part, that caused going over the max limit
|
60
|
+
counter -= 1
|
61
|
+
|
62
|
+
# Add the entities to new collection, so the total size is below max
|
63
|
+
if counter > 0
|
64
|
+
new_collection[:data] = collection[:data].shift(counter)
|
65
|
+
new_inventory[:collections] << new_collection
|
66
|
+
end
|
67
|
+
|
68
|
+
# Save the current batch
|
69
|
+
serialize_and_save_inventory(new_inventory)
|
70
|
+
parts += 1
|
71
|
+
|
72
|
+
# Create new data containers for a new batch
|
73
|
+
new_inventory = build_new_inventory(inventory)
|
74
|
+
new_collection = build_new_collection(collection)
|
75
|
+
wrapper_size = JSON.generate(new_inventory).size + JSON.generate(new_collection).size + 2
|
76
|
+
|
77
|
+
# Start with the data part we've removed from the currently saved payload
|
78
|
+
total_size = wrapper_size + data_size
|
79
|
+
counter = 1
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
# Store the rest of the collection
|
84
|
+
new_collection[:data] = collection[:data].shift(counter)
|
85
|
+
new_inventory[:collections] << new_collection
|
86
|
+
end
|
87
|
+
|
88
|
+
# save the rest
|
89
|
+
serialize_and_save_inventory(new_inventory)
|
90
|
+
parts += 1
|
91
|
+
|
92
|
+
return parts
|
93
|
+
end
|
94
|
+
|
95
|
+
def save_inventory(inventory_json)
|
96
|
+
client.save_inventory_with_http_info(inventory_json)
|
97
|
+
end
|
98
|
+
|
99
|
+
def serialize_and_save_inventory(inventory)
|
100
|
+
payload = JSON.generate(inventory)
|
101
|
+
if payload.size > max_bytes
|
102
|
+
raise Exception::EntityTooLarge,
|
103
|
+
"Entity is bigger than total limit and can't be split: #{payload}"
|
104
|
+
end
|
105
|
+
|
106
|
+
# Save the current batch
|
107
|
+
save_inventory(payload)
|
108
|
+
end
|
109
|
+
|
110
|
+
def build_new_collection(collection)
|
111
|
+
{:name => collection[:name], :data => []}
|
112
|
+
end
|
113
|
+
|
114
|
+
def build_new_inventory(inventory)
|
115
|
+
new_inventory = inventory.clone
|
116
|
+
new_inventory[:refresh_state_part_uuid] = SecureRandom.uuid
|
117
|
+
new_inventory[:collections] = []
|
118
|
+
new_inventory
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require "topological_inventory/providers/common/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "topological_inventory-providers-common"
|
8
|
+
spec.version = TopologicalInventory::Providers::Common::VERSION
|
9
|
+
spec.authors = ["Martin Slemr"]
|
10
|
+
spec.email = ["mslemr@redhat.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Common classes for topological-inventory collectors/operations}
|
13
|
+
spec.description = %q{Common classes for topological-inventory collectors/operations}
|
14
|
+
spec.homepage = "https://github.com/RedHatInsights/topological_inventory-providers-common"
|
15
|
+
spec.license = "Apache-2.0"
|
16
|
+
|
17
|
+
# Specify which files should be added to the gem when it is released.
|
18
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
19
|
+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
20
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
21
|
+
end
|
22
|
+
spec.bindir = "exe"
|
23
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
24
|
+
spec.require_paths = ["lib"]
|
25
|
+
|
26
|
+
spec.add_runtime_dependency 'config', '~> 1.7', '>= 1.7.2'
|
27
|
+
spec.add_runtime_dependency "activesupport", "~> 5.2.2"
|
28
|
+
spec.add_runtime_dependency "manageiq-loggers", "~> 0.4.0", ">= 0.4.2"
|
29
|
+
spec.add_runtime_dependency 'json', '~> 2.1', '>= 2.1.0'
|
30
|
+
spec.add_runtime_dependency "topological_inventory-api-client", "~> 3.0", ">= 3.0.1"
|
31
|
+
|
32
|
+
spec.add_development_dependency "bundler", "~> 2.0"
|
33
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
34
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
35
|
+
end
|
metadata
ADDED
@@ -0,0 +1,203 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: topological_inventory-providers-common
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Martin Slemr
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-04-22 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: config
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.7'
|
20
|
+
- - ">="
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: 1.7.2
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - "~>"
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '1.7'
|
30
|
+
- - ">="
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: 1.7.2
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: activesupport
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: 5.2.2
|
40
|
+
type: :runtime
|
41
|
+
prerelease: false
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - "~>"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: 5.2.2
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: manageiq-loggers
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 0.4.0
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: 0.4.2
|
57
|
+
type: :runtime
|
58
|
+
prerelease: false
|
59
|
+
version_requirements: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - "~>"
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: 0.4.0
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: 0.4.2
|
67
|
+
- !ruby/object:Gem::Dependency
|
68
|
+
name: json
|
69
|
+
requirement: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - "~>"
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '2.1'
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: 2.1.0
|
77
|
+
type: :runtime
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: !ruby/object:Gem::Requirement
|
80
|
+
requirements:
|
81
|
+
- - "~>"
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '2.1'
|
84
|
+
- - ">="
|
85
|
+
- !ruby/object:Gem::Version
|
86
|
+
version: 2.1.0
|
87
|
+
- !ruby/object:Gem::Dependency
|
88
|
+
name: topological_inventory-api-client
|
89
|
+
requirement: !ruby/object:Gem::Requirement
|
90
|
+
requirements:
|
91
|
+
- - "~>"
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '3.0'
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 3.0.1
|
97
|
+
type: :runtime
|
98
|
+
prerelease: false
|
99
|
+
version_requirements: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '3.0'
|
104
|
+
- - ">="
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
version: 3.0.1
|
107
|
+
- !ruby/object:Gem::Dependency
|
108
|
+
name: bundler
|
109
|
+
requirement: !ruby/object:Gem::Requirement
|
110
|
+
requirements:
|
111
|
+
- - "~>"
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
version: '2.0'
|
114
|
+
type: :development
|
115
|
+
prerelease: false
|
116
|
+
version_requirements: !ruby/object:Gem::Requirement
|
117
|
+
requirements:
|
118
|
+
- - "~>"
|
119
|
+
- !ruby/object:Gem::Version
|
120
|
+
version: '2.0'
|
121
|
+
- !ruby/object:Gem::Dependency
|
122
|
+
name: rake
|
123
|
+
requirement: !ruby/object:Gem::Requirement
|
124
|
+
requirements:
|
125
|
+
- - "~>"
|
126
|
+
- !ruby/object:Gem::Version
|
127
|
+
version: '10.0'
|
128
|
+
type: :development
|
129
|
+
prerelease: false
|
130
|
+
version_requirements: !ruby/object:Gem::Requirement
|
131
|
+
requirements:
|
132
|
+
- - "~>"
|
133
|
+
- !ruby/object:Gem::Version
|
134
|
+
version: '10.0'
|
135
|
+
- !ruby/object:Gem::Dependency
|
136
|
+
name: rspec
|
137
|
+
requirement: !ruby/object:Gem::Requirement
|
138
|
+
requirements:
|
139
|
+
- - "~>"
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: '3.0'
|
142
|
+
type: :development
|
143
|
+
prerelease: false
|
144
|
+
version_requirements: !ruby/object:Gem::Requirement
|
145
|
+
requirements:
|
146
|
+
- - "~>"
|
147
|
+
- !ruby/object:Gem::Version
|
148
|
+
version: '3.0'
|
149
|
+
description: Common classes for topological-inventory collectors/operations
|
150
|
+
email:
|
151
|
+
- mslemr@redhat.com
|
152
|
+
executables: []
|
153
|
+
extensions: []
|
154
|
+
extra_rdoc_files: []
|
155
|
+
files:
|
156
|
+
- ".gitignore"
|
157
|
+
- ".rspec"
|
158
|
+
- ".travis.yml"
|
159
|
+
- Gemfile
|
160
|
+
- LICENSE.txt
|
161
|
+
- README.md
|
162
|
+
- Rakefile
|
163
|
+
- bin/console
|
164
|
+
- bin/setup
|
165
|
+
- lib/topological_inventory/providers/common.rb
|
166
|
+
- lib/topological_inventory/providers/common/collector.rb
|
167
|
+
- lib/topological_inventory/providers/common/collector/inventory_collection_storage.rb
|
168
|
+
- lib/topological_inventory/providers/common/collector/inventory_collection_wrapper.rb
|
169
|
+
- lib/topological_inventory/providers/common/collector/parser.rb
|
170
|
+
- lib/topological_inventory/providers/common/collectors_pool.rb
|
171
|
+
- lib/topological_inventory/providers/common/logging.rb
|
172
|
+
- lib/topological_inventory/providers/common/operations/endpoint_client.rb
|
173
|
+
- lib/topological_inventory/providers/common/operations/processor.rb
|
174
|
+
- lib/topological_inventory/providers/common/operations/sources_api_client.rb
|
175
|
+
- lib/topological_inventory/providers/common/operations/topology_api_client.rb
|
176
|
+
- lib/topological_inventory/providers/common/save_inventory/exception.rb
|
177
|
+
- lib/topological_inventory/providers/common/save_inventory/saver.rb
|
178
|
+
- lib/topological_inventory/providers/common/version.rb
|
179
|
+
- topological_inventory-providers-common.gemspec
|
180
|
+
homepage: https://github.com/RedHatInsights/topological_inventory-providers-common
|
181
|
+
licenses:
|
182
|
+
- Apache-2.0
|
183
|
+
metadata: {}
|
184
|
+
post_install_message:
|
185
|
+
rdoc_options: []
|
186
|
+
require_paths:
|
187
|
+
- lib
|
188
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
189
|
+
requirements:
|
190
|
+
- - ">="
|
191
|
+
- !ruby/object:Gem::Version
|
192
|
+
version: '0'
|
193
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
194
|
+
requirements:
|
195
|
+
- - ">="
|
196
|
+
- !ruby/object:Gem::Version
|
197
|
+
version: '0'
|
198
|
+
requirements: []
|
199
|
+
rubygems_version: 3.1.2
|
200
|
+
signing_key:
|
201
|
+
specification_version: 4
|
202
|
+
summary: Common classes for topological-inventory collectors/operations
|
203
|
+
test_files: []
|