foreman_rh_cloud 12.1.3 → 12.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +3 -5
- data/app/controllers/concerns/insights_cloud/package_profile_upload_extensions.rb +33 -0
- data/app/controllers/insights_cloud/api/machine_telemetries_controller.rb +1 -1
- data/app/services/foreman_rh_cloud/cert_auth.rb +9 -1
- data/app/services/foreman_rh_cloud/cloud_request_forwarder.rb +8 -3
- data/app/views/api/v2/hosts/insights/base.rabl +6 -0
- data/lib/foreman_inventory_upload/async/generate_report_job.rb +11 -5
- data/lib/foreman_inventory_upload/async/upload_report_job.rb +15 -4
- data/lib/foreman_inventory_upload/generators/archived_report.rb +2 -2
- data/lib/foreman_inventory_upload/generators/fact_helpers.rb +65 -13
- data/lib/foreman_inventory_upload/generators/queries.rb +7 -5
- data/lib/foreman_inventory_upload/generators/slice.rb +0 -1
- data/lib/foreman_inventory_upload.rb +2 -2
- data/lib/foreman_rh_cloud/engine.rb +3 -2
- data/lib/foreman_rh_cloud/version.rb +1 -1
- data/lib/inventory_sync/async/inventory_hosts_sync.rb +2 -0
- data/lib/tasks/rh_cloud_inventory.rake +3 -2
- data/package.json +1 -1
- data/test/unit/archived_report_generator_test.rb +1 -1
- data/test/unit/fact_helpers_test.rb +267 -2
- data/test/unit/services/foreman_rh_cloud/cloud_request_forwarder_test.rb +19 -2
- data/test/unit/slice_generator_test.rb +69 -10
- data/webpack/ForemanColumnExtensions/index.js +41 -0
- data/webpack/global_index.js +3 -0
- metadata +3 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7ed52afd2a65d3cc87c3ae660716ca1044af88b359f84f108cbe005c01a37a42
|
4
|
+
data.tar.gz: b00def287ae973805693edaf6b9eb13e72846992bcb7e5432073ff722458d565
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3ba0a99ac74b2b311f3569cc6dee79a5043be05abdfe41ca25e36f9c4a6e1d903732e8e38ae580c01634842ec493e8ab350f0b3d04b680e032aba9e730bd1935
|
7
|
+
data.tar.gz: 147d7da2c6352b9333bf1a5c8537c1468d2246d7bd656846ff515170b7c3841c55c73ae4e7fef690c39f56d893721efa994e271b4214d154f9542e03da43c808
|
data/README.md
CHANGED
@@ -1,18 +1,16 @@
|
|
1
1
|
[](https://github.com/theforeman/foreman_rh_cloud/actions/workflows/ruby_tests.yml)
|
2
2
|
[](https://github.com/theforeman/foreman_rh_cloud/actions/workflows/js_tests.yml)
|
3
|
+
[](https://deepwiki.com/theforeman/foreman_rh_cloud)
|
3
4
|
|
4
5
|
# ForemanRhCloud
|
5
6
|
|
6
|
-
*Introduction here*
|
7
|
-
|
8
7
|
## Installation
|
9
8
|
|
10
9
|
See [How_to_Install_a_Plugin](http://projects.theforeman.org/projects/foreman/wiki/How_to_Install_a_Plugin)
|
11
10
|
for how to install Foreman plugins
|
12
11
|
|
13
|
-
##
|
14
|
-
|
15
|
-
*Usage here*
|
12
|
+
## Project overview
|
13
|
+
See our [wiki](https://deepwiki.com/theforeman/foreman_rh_cloud)
|
16
14
|
|
17
15
|
### In Satellite
|
18
16
|
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module InsightsCloud
|
2
|
+
module PackageProfileUploadExtensions
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
# This method explicitly listens on Katello actions
|
7
|
+
# rubocop:disable Rails/LexicallyScopedActionFilter
|
8
|
+
after_action :generate_host_report, only: [:upload_package_profile, :upload_profiles]
|
9
|
+
# rubocop:enable Rails/LexicallyScopedActionFilter
|
10
|
+
end
|
11
|
+
|
12
|
+
def generate_host_report
|
13
|
+
return unless ForemanRhCloud.with_local_advisor_engine?
|
14
|
+
|
15
|
+
logger.debug("Generating host-specific report for host #{@host.name}")
|
16
|
+
|
17
|
+
ForemanTasks.async_task(
|
18
|
+
ForemanInventoryUpload::Async::GenerateReportJob,
|
19
|
+
ForemanInventoryUpload.generated_reports_folder,
|
20
|
+
@host.organization_id,
|
21
|
+
false,
|
22
|
+
"id=#{@host.id}"
|
23
|
+
)
|
24
|
+
|
25
|
+
# in IoP case, the hosts are identified by the sub-man ID, and we can assume they already
|
26
|
+
# exist in the local inventory. This will also handle facet creation for new hosts.
|
27
|
+
return if @host.insights
|
28
|
+
|
29
|
+
insights_facet = @host.build_insights(uuid: @host.subscription_facet.uuid)
|
30
|
+
insights_facet.save
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -17,7 +17,7 @@ module InsightsCloud::Api
|
|
17
17
|
def forward_request
|
18
18
|
certs = candlepin_id_cert @organization
|
19
19
|
begin
|
20
|
-
@cloud_response = ::ForemanRhCloud::CloudRequestForwarder.new.forward_request(request, controller_name, @branch_id, certs)
|
20
|
+
@cloud_response = ::ForemanRhCloud::CloudRequestForwarder.new.forward_request(request, controller_name, @branch_id, certs, @host)
|
21
21
|
rescue RestClient::Exceptions::Timeout => e
|
22
22
|
response_obj = e.response.presence || e.exception
|
23
23
|
return render json: { message: response_obj.to_s, error: response_obj.to_s }, status: :gateway_timeout
|
@@ -10,7 +10,8 @@ module ForemanRhCloud
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def execute_cloud_request(params)
|
13
|
-
|
13
|
+
organization = params.delete(:organization)
|
14
|
+
certs = ForemanRhCloud.with_local_advisor_engine? ? foreman_certificate : candlepin_id_cert(organization)
|
14
15
|
final_params = {
|
15
16
|
ssl_client_cert: OpenSSL::X509::Certificate.new(certs[:cert]),
|
16
17
|
ssl_client_key: OpenSSL::PKey.read(certs[:key]),
|
@@ -18,5 +19,12 @@ module ForemanRhCloud
|
|
18
19
|
|
19
20
|
super(final_params)
|
20
21
|
end
|
22
|
+
|
23
|
+
def foreman_certificate
|
24
|
+
@foreman_certificate ||= {
|
25
|
+
cert: File.read(Setting[:ssl_certificate]),
|
26
|
+
key: File.read(Setting[:ssl_priv_key]),
|
27
|
+
}
|
28
|
+
end
|
21
29
|
end
|
22
30
|
end
|
@@ -4,7 +4,7 @@ module ForemanRhCloud
|
|
4
4
|
class CloudRequestForwarder
|
5
5
|
include ForemanRhCloud::CloudRequest
|
6
6
|
|
7
|
-
def forward_request(original_request, controller_name, branch_id, certs)
|
7
|
+
def forward_request(original_request, controller_name, branch_id, certs, host)
|
8
8
|
forward_params = prepare_forward_params(original_request, branch_id)
|
9
9
|
logger.debug("Request parameters for telemetry request: #{forward_params}")
|
10
10
|
|
@@ -12,14 +12,14 @@ module ForemanRhCloud
|
|
12
12
|
|
13
13
|
logger.debug("User agent for telemetry is: #{http_user_agent original_request}")
|
14
14
|
|
15
|
-
request_opts = prepare_request_opts(original_request, forward_payload, forward_params, certs)
|
15
|
+
request_opts = prepare_request_opts(original_request, forward_payload, forward_params, certs, host)
|
16
16
|
|
17
17
|
logger.debug("Sending request to: #{request_opts[:url]}")
|
18
18
|
|
19
19
|
execute_cloud_request(request_opts)
|
20
20
|
end
|
21
21
|
|
22
|
-
def prepare_request_opts(original_request, forward_payload, forward_params, certs)
|
22
|
+
def prepare_request_opts(original_request, forward_payload, forward_params, certs, host)
|
23
23
|
base_params = {
|
24
24
|
method: original_request.method,
|
25
25
|
payload: forward_payload,
|
@@ -28,6 +28,7 @@ module ForemanRhCloud
|
|
28
28
|
params: forward_params,
|
29
29
|
user_agent: http_user_agent(original_request),
|
30
30
|
content_type: original_request.media_type.presence || original_request.format.to_s,
|
31
|
+
Forwarded: prepare_forwarded_header(host),
|
31
32
|
}
|
32
33
|
),
|
33
34
|
}
|
@@ -105,6 +106,10 @@ module ForemanRhCloud
|
|
105
106
|
headers
|
106
107
|
end
|
107
108
|
|
109
|
+
def prepare_forwarded_header(host)
|
110
|
+
"for=\"_#{host.subscription_facet.uuid}\""
|
111
|
+
end
|
112
|
+
|
108
113
|
def lightspeed?
|
109
114
|
->(request_path) { request_path.include? '/lightspeed' }
|
110
115
|
end
|
@@ -3,3 +3,9 @@ attributes :uuid
|
|
3
3
|
node :insights_hit_details do |facet|
|
4
4
|
facet&.host&.facts('insights::hit_details')&.values&.first
|
5
5
|
end
|
6
|
+
node :insights_hits_count do |facet|
|
7
|
+
facet.hits&.count
|
8
|
+
end
|
9
|
+
node :use_local_advisor_engine do |_facet|
|
10
|
+
ForemanRhCloud.with_local_advisor_engine?
|
11
|
+
end
|
@@ -5,18 +5,19 @@ module ForemanInventoryUpload
|
|
5
5
|
"report_for_#{label}"
|
6
6
|
end
|
7
7
|
|
8
|
-
def plan(base_folder, organization_id, disconnected)
|
8
|
+
def plan(base_folder, organization_id, disconnected, hosts_filter = nil)
|
9
9
|
sequence do
|
10
10
|
super(
|
11
|
-
GenerateReportJob.output_label(organization_id),
|
11
|
+
GenerateReportJob.output_label("#{organization_id}#{hosts_filter.empty? ? nil : "[#{hosts_filter.to_s.parameterize}]"}"),
|
12
12
|
organization_id: organization_id,
|
13
|
-
base_folder: base_folder
|
13
|
+
base_folder: base_folder,
|
14
|
+
hosts_filter: hosts_filter
|
14
15
|
)
|
15
16
|
|
16
17
|
plan_action(
|
17
18
|
QueueForUploadJob,
|
18
19
|
base_folder,
|
19
|
-
ForemanInventoryUpload.facts_archive_name(organization_id),
|
20
|
+
ForemanInventoryUpload.facts_archive_name(organization_id, hosts_filter),
|
20
21
|
organization_id,
|
21
22
|
disconnected
|
22
23
|
)
|
@@ -34,7 +35,8 @@ module ForemanInventoryUpload
|
|
34
35
|
def env
|
35
36
|
super.merge(
|
36
37
|
'target' => base_folder,
|
37
|
-
'organization_id' => organization_id
|
38
|
+
'organization_id' => organization_id,
|
39
|
+
'hosts_filter' => hosts_filter
|
38
40
|
)
|
39
41
|
end
|
40
42
|
|
@@ -45,6 +47,10 @@ module ForemanInventoryUpload
|
|
45
47
|
def organization_id
|
46
48
|
input[:organization_id]
|
47
49
|
end
|
50
|
+
|
51
|
+
def hosts_filter
|
52
|
+
input[:hosts_filter]
|
53
|
+
end
|
48
54
|
end
|
49
55
|
end
|
50
56
|
end
|
@@ -33,8 +33,8 @@ module ForemanInventoryUpload
|
|
33
33
|
end
|
34
34
|
|
35
35
|
Tempfile.create([organization.name, '.pem']) do |cer_file|
|
36
|
-
cer_file.write(
|
37
|
-
cer_file.write(
|
36
|
+
cer_file.write(certificate[:cert])
|
37
|
+
cer_file.write(certificate[:key])
|
38
38
|
cer_file.flush
|
39
39
|
@cer_path = cer_file.path
|
40
40
|
super
|
@@ -59,8 +59,12 @@ module ForemanInventoryUpload
|
|
59
59
|
env_vars
|
60
60
|
end
|
61
61
|
|
62
|
-
def
|
63
|
-
|
62
|
+
def certificate
|
63
|
+
ForemanRhCloud.with_local_advisor_engine? ? foreman_certificate : manifest_certificate
|
64
|
+
end
|
65
|
+
|
66
|
+
def manifest_certificate
|
67
|
+
@manifest_certificate ||= begin
|
64
68
|
candlepin_id_certificate = organization.owner_details['upstreamConsumer']['idCert']
|
65
69
|
{
|
66
70
|
cert: candlepin_id_certificate['cert'],
|
@@ -69,6 +73,13 @@ module ForemanInventoryUpload
|
|
69
73
|
end
|
70
74
|
end
|
71
75
|
|
76
|
+
def foreman_certificate
|
77
|
+
@foreman_certificate ||= {
|
78
|
+
cert: File.read(Setting[:ssl_certificate]),
|
79
|
+
key: File.read(Setting[:ssl_priv_key]),
|
80
|
+
}
|
81
|
+
end
|
82
|
+
|
72
83
|
def filename
|
73
84
|
input[:filename]
|
74
85
|
end
|
@@ -6,10 +6,10 @@ module ForemanInventoryUpload
|
|
6
6
|
@logger = logger
|
7
7
|
end
|
8
8
|
|
9
|
-
def render(organization:)
|
9
|
+
def render(organization:, filter: nil)
|
10
10
|
Dir.mktmpdir do |tmpdir|
|
11
11
|
@logger.info "Started generating hosts report in #{tmpdir}"
|
12
|
-
host_batches = ForemanInventoryUpload::Generators::Queries.for_org(organization)
|
12
|
+
host_batches = ForemanInventoryUpload::Generators::Queries.for_org(organization, hosts_query: filter || '')
|
13
13
|
File.open(File.join(tmpdir, 'metadata.json'), 'w') do |metadata_out|
|
14
14
|
metadata_generator = ForemanInventoryUpload::Generators::Metadata.new(metadata_out)
|
15
15
|
metadata_generator.render do |inner_generator|
|
@@ -57,17 +57,39 @@ module ForemanInventoryUpload
|
|
57
57
|
end
|
58
58
|
|
59
59
|
def obfuscate_hostname?(host)
|
60
|
+
# Returns true if hostname obfuscation should be applied for a given host, based on hierarchy:
|
61
|
+
# 1. Global setting for hostname obfuscation.
|
62
|
+
return true if Setting[:obfuscate_inventory_hostnames]
|
63
|
+
|
60
64
|
insights_client_setting = fact_value(host, 'insights_client::obfuscate_hostname_enabled')
|
61
65
|
insights_client_setting = ActiveModel::Type::Boolean.new.cast(insights_client_setting)
|
62
|
-
return insights_client_setting unless insights_client_setting.nil?
|
63
66
|
|
64
|
-
|
67
|
+
# 2. host fact reported by insights_client
|
68
|
+
# 3. if neither of the above, don't obfuscate.
|
69
|
+
insights_client_setting.nil? ? false : insights_client_setting
|
65
70
|
end
|
66
71
|
|
67
72
|
def fqdn(host)
|
68
|
-
|
69
|
-
|
70
|
-
|
73
|
+
if obfuscate_hostname?(host)
|
74
|
+
# If obfuscation is enabled, attempt to retrieve an already obfuscated hostname
|
75
|
+
# from the 'insights_client::obfuscated_hostname' fact.
|
76
|
+
# Example format of `parsed_insights_array`:
|
77
|
+
# [{"original"=>"host.example.com", "obfuscated"=>"0dd449d0a027.example.com"},
|
78
|
+
# {"original"=>"satellite.example.com", "obfuscated"=>"host2.example.com"}]
|
79
|
+
begin
|
80
|
+
parsed_insights_array = JSON.parse(fact_value(host, 'insights_client::obfuscated_hostname') || '[]')
|
81
|
+
rescue JSON::ParserError
|
82
|
+
parsed_insights_array = []
|
83
|
+
end
|
84
|
+
# Obfuscate using the following hierarchy:
|
85
|
+
# 1. the obfuscated_hostname fact sent by insights_client
|
86
|
+
parsed_insights_item = parsed_insights_array.find { |item| item['original'] == host.fqdn }
|
87
|
+
# 2. our own helper method
|
88
|
+
parsed_insights_item&.[]('obfuscated') || obfuscate_fqdn(host.fqdn)
|
89
|
+
else
|
90
|
+
# If hostname obfuscation is not enabled for this host, return the host's original FQDN.
|
91
|
+
host.fqdn
|
92
|
+
end
|
71
93
|
end
|
72
94
|
|
73
95
|
def obfuscate_fqdn(fqdn)
|
@@ -75,35 +97,65 @@ module ForemanInventoryUpload
|
|
75
97
|
end
|
76
98
|
|
77
99
|
def obfuscate_ips?(host)
|
78
|
-
|
79
|
-
|
80
|
-
return
|
100
|
+
# Returns true if IP obfuscation should be applied for a given host, based on hierarchy:
|
101
|
+
# 1. Global setting for IP obfuscation.
|
102
|
+
return true if Setting[:obfuscate_inventory_ips]
|
81
103
|
|
82
|
-
|
104
|
+
insights_client_ipv4_setting = fact_value(host, 'insights_client::obfuscate_ipv4_enabled')
|
105
|
+
insights_client_ipv6_setting = fact_value(host, 'insights_client::obfuscate_ipv6_enabled')
|
106
|
+
|
107
|
+
cast_ipv4_setting = ActiveModel::Type::Boolean.new.cast(insights_client_ipv4_setting)
|
108
|
+
cast_ipv6_setting = ActiveModel::Type::Boolean.new.cast(insights_client_ipv6_setting)
|
109
|
+
|
110
|
+
# 2. The host's IPv4 or IPv6 obfuscation fact value is true
|
111
|
+
# 3. If neither of the above, don't obfuscate.
|
112
|
+
cast_ipv4_setting || cast_ipv6_setting || false
|
83
113
|
end
|
84
114
|
|
85
115
|
def host_ips(host)
|
116
|
+
# Determines and returns the IP addresses associated with a host, applying obfuscation if enabled.
|
117
|
+
|
118
|
+
# If IP obfuscation is enabled for the host return a representation of obfuscated IP addresses.
|
86
119
|
return obfuscated_ips(host) if obfuscate_ips?(host)
|
87
120
|
|
88
|
-
#
|
121
|
+
# If IP obfuscation is NOT needed, return a special kind of Hash.
|
122
|
+
# where when you try to access a key in it
|
123
|
+
# if the key doesn't exist, it simply returns the key itself.
|
124
|
+
# This is useful because it means if you try to get an IP from this hash,
|
125
|
+
# you'll just get the original IP back. It allows the calling code to
|
126
|
+
# use the same interface whether obfuscation is applied or not.
|
89
127
|
Hash.new { |h, k| k }
|
90
128
|
end
|
91
129
|
|
92
130
|
def obfuscated_ips(host)
|
93
|
-
|
131
|
+
# Example format of `parsed_insights_array`:
|
132
|
+
# [{"original": "192.168.1.10", "obfuscated": "10.230.230.1"},
|
133
|
+
# {"original": "192.168.1.11", "obfuscated": "10.230.230.2"}]
|
134
|
+
begin
|
135
|
+
parsed_insights_array = JSON.parse(fact_value(host, 'insights_client::obfuscated_ipv4') || '[]')
|
136
|
+
rescue JSON::ParserError
|
137
|
+
parsed_insights_array = []
|
138
|
+
end
|
94
139
|
|
140
|
+
# Create a new Hash to store the mapping from original IP addresses to their obfuscated versions.
|
141
|
+
# where the 'original' IP is the key and the 'obfuscated' IP is the value.
|
95
142
|
obfuscated_ips = Hash[
|
96
|
-
|
143
|
+
parsed_insights_array.map { |ip_record| [ip_record['original'], ip_record['obfuscated']] }
|
97
144
|
]
|
98
145
|
|
146
|
+
# Sets a default proc for the obfuscated_ips hash.
|
147
|
+
# When a key is accessed that does not exist in the hash, this proc is called.
|
148
|
+
# It assigns the result of obfuscate_ip(key, hash) to the missing key in the hash.
|
149
|
+
# This ensures that any missing IP address key will be obfuscated and stored automatically.
|
99
150
|
obfuscated_ips.default_proc = proc do |hash, key|
|
100
151
|
hash[key] = obfuscate_ip(key, hash)
|
101
152
|
end
|
102
|
-
|
103
153
|
obfuscated_ips
|
104
154
|
end
|
105
155
|
|
106
156
|
def obfuscate_ip(ip, ips_dict)
|
157
|
+
# Produce a new, unique obfuscated IP that is
|
158
|
+
# numerically one greater than the highest existing obfuscated IP
|
107
159
|
max_obfuscated = ips_dict.values.map { |v| IPAddr.new(v).to_i }.max || IPAddr.new('10.230.230.0').to_i
|
108
160
|
|
109
161
|
IPAddr.new(max_obfuscated + 1, Socket::AF_INET).to_s
|
@@ -26,9 +26,11 @@ module ForemanInventoryUpload
|
|
26
26
|
'dmi::system::product_name',
|
27
27
|
'dmi::chassis::asset_tag',
|
28
28
|
'insights_client::obfuscate_hostname_enabled',
|
29
|
-
'insights_client::
|
30
|
-
'insights_client::
|
31
|
-
'insights_client::
|
29
|
+
'insights_client::obfuscate_ipv4_enabled',
|
30
|
+
'insights_client::obfuscate_ipv6_enabled',
|
31
|
+
'insights_client::obfuscated_ipv4',
|
32
|
+
'insights_client::obfuscated_ipv6',
|
33
|
+
'insights_client::obfuscated_hostname',
|
32
34
|
'insights_id',
|
33
35
|
'conversions::activity',
|
34
36
|
'conversions::packages::0::nevra',
|
@@ -58,8 +60,8 @@ module ForemanInventoryUpload
|
|
58
60
|
)
|
59
61
|
end
|
60
62
|
|
61
|
-
def self.for_org(organization_id, use_batches: true)
|
62
|
-
base_query = for_slice(Host.unscoped.where(organization_id: organization_id))
|
63
|
+
def self.for_org(organization_id, use_batches: true, hosts_query: '')
|
64
|
+
base_query = for_slice(Host.unscoped.where(organization_id: organization_id).search_for(hosts_query))
|
63
65
|
use_batches ? base_query.in_batches(of: ForemanInventoryUpload.slice_size) : base_query
|
64
66
|
end
|
65
67
|
end
|
@@ -190,7 +190,6 @@ module ForemanInventoryUpload
|
|
190
190
|
) { |v| os_release_value(*v) }
|
191
191
|
@stream.simple_field('os_kernel_version', fact_value(host, 'uname::release'))
|
192
192
|
@stream.simple_field('arch', host.architecture&.name)
|
193
|
-
@stream.simple_field('katello_agent_running', false)
|
194
193
|
@stream.simple_field(
|
195
194
|
'infrastructure_type',
|
196
195
|
ActiveModel::Type::Boolean.new.cast(fact_value(host, 'virt::is_guest')) ? 'virtual' : 'physical'
|
@@ -52,8 +52,8 @@ module ForemanInventoryUpload
|
|
52
52
|
'uploader.sh'
|
53
53
|
end
|
54
54
|
|
55
|
-
def self.facts_archive_name(organization)
|
56
|
-
"report_for_#{organization}.tar.xz"
|
55
|
+
def self.facts_archive_name(organization, filter = nil)
|
56
|
+
"report_for_#{organization}#{filter.empty? ? nil : "[#{filter.to_s.parameterize}]"}.tar.xz"
|
57
57
|
end
|
58
58
|
|
59
59
|
def self.upload_url
|
@@ -114,8 +114,7 @@ module ForemanRhCloud
|
|
114
114
|
caption: N_('Inventory Upload'),
|
115
115
|
url: '/foreman_rh_cloud/inventory_upload',
|
116
116
|
url_hash: { controller: :react, action: :index },
|
117
|
-
parent: :insights_menu
|
118
|
-
if: -> { !ForemanRhCloud.with_local_advisor_engine? }
|
117
|
+
parent: :insights_menu
|
119
118
|
menu :top_menu, :insights_hits, caption: N_('Recommendations'), url: '/foreman_rh_cloud/insights_cloud', url_hash: { controller: :react, action: :index }, parent: :insights_menu
|
120
119
|
menu :top_menu,
|
121
120
|
:insights_vulnerability,
|
@@ -165,6 +164,8 @@ module ForemanRhCloud
|
|
165
164
|
::Katello::UINotifications::Subscriptions::ManifestImportSuccess.include ForemanInventoryUpload::Notifications::ManifestImportSuccessNotificationOverride if defined?(Katello)
|
166
165
|
|
167
166
|
::Host::Managed.include RhCloudHost
|
167
|
+
|
168
|
+
::Katello::Api::Rhsm::CandlepinDynflowProxyController.include InsightsCloud::PackageProfileUploadExtensions
|
168
169
|
end
|
169
170
|
end
|
170
171
|
|
@@ -8,6 +8,8 @@ module InventorySync
|
|
8
8
|
set_callback :step, :around, :create_missing_hosts
|
9
9
|
|
10
10
|
def plan(organizations)
|
11
|
+
# Do not run for local advisor, since we use sub-man id to identify hosts.
|
12
|
+
return if ForemanRhCloud.with_local_advisor_engine?
|
11
13
|
# by default the tasks will be executed concurrently
|
12
14
|
super(organizations)
|
13
15
|
plan_self_host_sync
|
@@ -26,6 +26,7 @@ namespace :rh_cloud_inventory do
|
|
26
26
|
task generate: :environment do
|
27
27
|
organizations = [ENV['organization_id']]
|
28
28
|
base_folder = ENV['target'] || Dir.pwd
|
29
|
+
filter = ENV['hosts_filter']
|
29
30
|
|
30
31
|
unless File.writable?(base_folder)
|
31
32
|
puts "#{base_folder} is not writable by the current process"
|
@@ -40,9 +41,9 @@ namespace :rh_cloud_inventory do
|
|
40
41
|
|
41
42
|
User.as_anonymous_admin do
|
42
43
|
organizations.each do |organization|
|
43
|
-
target = File.join(base_folder, ForemanInventoryUpload.facts_archive_name(organization))
|
44
|
+
target = File.join(base_folder, ForemanInventoryUpload.facts_archive_name(organization, filter))
|
44
45
|
archived_report_generator = ForemanInventoryUpload::Generators::ArchivedReport.new(target, Logger.new(STDOUT))
|
45
|
-
archived_report_generator.render(organization: organization)
|
46
|
+
archived_report_generator.render(organization: organization, filter: filter)
|
46
47
|
puts "Successfully generated #{target} for organization id #{organization}"
|
47
48
|
end
|
48
49
|
end
|
data/package.json
CHANGED
@@ -50,7 +50,7 @@ class ArchivedReportGeneratorTest < ActiveSupport::TestCase
|
|
50
50
|
batches = Host.where(id: @host.id).in_batches
|
51
51
|
test_org = FactoryBot.create(:organization)
|
52
52
|
|
53
|
-
ForemanInventoryUpload::Generators::Queries.expects(:for_org).with(test_org.id).returns(batches)
|
53
|
+
ForemanInventoryUpload::Generators::Queries.expects(:for_org).with(test_org.id, hosts_query: '').returns(batches)
|
54
54
|
ForemanInventoryUpload::Generators::Slice.any_instance.stubs(:golden_ticket?).returns(false)
|
55
55
|
Dir.mktmpdir do |tmpdir|
|
56
56
|
target = File.join(tmpdir, 'test.tar.gz')
|
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'test_plugin_helper'
|
2
|
+
require 'digest'
|
2
3
|
|
3
4
|
class FactHelpersTest < ActiveSupport::TestCase
|
4
5
|
class FactsHelpersTestStub
|
@@ -29,7 +30,7 @@ class FactHelpersTest < ActiveSupport::TestCase
|
|
29
30
|
|
30
31
|
test 'obfuscates ips with insights-client data' do
|
31
32
|
host = mock('host')
|
32
|
-
@instance.expects(:fact_value).with(host, 'insights_client::
|
33
|
+
@instance.expects(:fact_value).with(host, 'insights_client::obfuscated_ipv4').returns(
|
33
34
|
'[{"obfuscated": "10.230.230.1", "original": "224.0.0.1"}, {"obfuscated": "10.230.230.255", "original": "224.0.0.251"}]'
|
34
35
|
)
|
35
36
|
|
@@ -41,11 +42,275 @@ class FactHelpersTest < ActiveSupport::TestCase
|
|
41
42
|
|
42
43
|
test 'obfuscates ips without insights-client data' do
|
43
44
|
host = mock('host')
|
44
|
-
@instance.expects(:fact_value).with(host, 'insights_client::
|
45
|
+
@instance.expects(:fact_value).with(host, 'insights_client::obfuscated_ipv4').returns(nil)
|
45
46
|
|
46
47
|
actual = @instance.obfuscated_ips(host)
|
47
48
|
|
48
49
|
assert_equal '10.230.230.1', actual['224.0.0.1']
|
49
50
|
assert_equal '10.230.230.2', actual['224.0.0.2']
|
50
51
|
end
|
52
|
+
|
53
|
+
describe 'obfuscate_hostname?' do
|
54
|
+
test 'returns true when global setting is enabled' do
|
55
|
+
Setting.expects(:[]).with(:obfuscate_inventory_hostnames).returns(true)
|
56
|
+
host = mock('host')
|
57
|
+
|
58
|
+
result = @instance.obfuscate_hostname?(host)
|
59
|
+
|
60
|
+
assert result
|
61
|
+
end
|
62
|
+
|
63
|
+
test 'returns false when global setting is disabled and no host-specific setting' do
|
64
|
+
Setting.expects(:[]).with(:obfuscate_inventory_hostnames).returns(false)
|
65
|
+
host = mock('host')
|
66
|
+
@instance.expects(:fact_value).with(host, 'insights_client::obfuscate_hostname_enabled').returns(nil)
|
67
|
+
|
68
|
+
result = @instance.obfuscate_hostname?(host)
|
69
|
+
|
70
|
+
refute result
|
71
|
+
end
|
72
|
+
|
73
|
+
test 'returns true when host-specific setting is enabled' do
|
74
|
+
Setting.expects(:[]).with(:obfuscate_inventory_hostnames).returns(false)
|
75
|
+
host = mock('host')
|
76
|
+
@instance.expects(:fact_value).with(host, 'insights_client::obfuscate_hostname_enabled').returns('true')
|
77
|
+
|
78
|
+
result = @instance.obfuscate_hostname?(host)
|
79
|
+
|
80
|
+
assert result
|
81
|
+
end
|
82
|
+
|
83
|
+
test 'returns false when host-specific setting is disabled' do
|
84
|
+
Setting.expects(:[]).with(:obfuscate_inventory_hostnames).returns(false)
|
85
|
+
host = mock('host')
|
86
|
+
@instance.expects(:fact_value).with(host, 'insights_client::obfuscate_hostname_enabled').returns('false')
|
87
|
+
|
88
|
+
result = @instance.obfuscate_hostname?(host)
|
89
|
+
|
90
|
+
refute result
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
describe 'fqdn' do
|
95
|
+
test 'returns original fqdn when obfuscation is disabled' do
|
96
|
+
host = mock('host')
|
97
|
+
host.expects(:fqdn).returns('test.example.com')
|
98
|
+
@instance.expects(:obfuscate_hostname?).with(host).returns(false)
|
99
|
+
|
100
|
+
result = @instance.fqdn(host)
|
101
|
+
|
102
|
+
assert_equal 'test.example.com', result
|
103
|
+
end
|
104
|
+
|
105
|
+
test 'returns obfuscated hostname from insights_client fact when available' do
|
106
|
+
host = mock('host')
|
107
|
+
host.expects(:fqdn).returns('test.example.com').once
|
108
|
+
@instance.expects(:obfuscate_hostname?).with(host).returns(true)
|
109
|
+
@instance.expects(:fact_value).with(host, 'insights_client::obfuscated_hostname').returns(
|
110
|
+
'[{"original": "test.example.com", "obfuscated": "abc123.example.com"}]'
|
111
|
+
)
|
112
|
+
|
113
|
+
result = @instance.fqdn(host)
|
114
|
+
|
115
|
+
assert_equal 'abc123.example.com', result
|
116
|
+
end
|
117
|
+
|
118
|
+
test 'returns dynamically obfuscated hostname when insights_client fact is not available' do
|
119
|
+
host = mock('host')
|
120
|
+
host.stubs(:fqdn).returns('test.example.com')
|
121
|
+
@instance.expects(:obfuscate_hostname?).with(host).returns(true)
|
122
|
+
@instance.expects(:fact_value).with(host, 'insights_client::obfuscated_hostname').returns(nil)
|
123
|
+
|
124
|
+
result = @instance.fqdn(host)
|
125
|
+
|
126
|
+
expected = "#{Digest::SHA1.hexdigest('test.example.com')}.example.com"
|
127
|
+
assert_equal expected, result
|
128
|
+
end
|
129
|
+
|
130
|
+
test 'returns dynamically obfuscated hostname when insights_client fact does not contain matching host' do
|
131
|
+
host = mock('host')
|
132
|
+
host.expects(:fqdn).returns('test.example.com').twice
|
133
|
+
@instance.expects(:obfuscate_hostname?).with(host).returns(true)
|
134
|
+
@instance.expects(:fact_value).with(host, 'insights_client::obfuscated_hostname').returns(
|
135
|
+
'[{"original": "other.example.com", "obfuscated": "abc123.example.com"}]'
|
136
|
+
)
|
137
|
+
@instance.expects(:obfuscate_fqdn).with('test.example.com').returns('dynamically_obfuscated.example.com')
|
138
|
+
|
139
|
+
result = @instance.fqdn(host)
|
140
|
+
|
141
|
+
assert_equal 'dynamically_obfuscated.example.com', result
|
142
|
+
end
|
143
|
+
|
144
|
+
test 'handles invalid JSON in insights_client fact gracefully' do
|
145
|
+
host = mock('host')
|
146
|
+
host.stubs(:fqdn).returns('test.example.com')
|
147
|
+
@instance.expects(:obfuscate_hostname?).with(host).returns(true)
|
148
|
+
@instance.expects(:fact_value).with(host, 'insights_client::obfuscated_hostname').returns('invalid json')
|
149
|
+
|
150
|
+
result = @instance.fqdn(host)
|
151
|
+
|
152
|
+
expected = "#{Digest::SHA1.hexdigest('test.example.com')}.example.com"
|
153
|
+
assert_equal expected, result
|
154
|
+
end
|
155
|
+
|
156
|
+
test 'handles empty insights_client fact' do
|
157
|
+
host = mock('host')
|
158
|
+
host.stubs(:fqdn).returns('test.example.com')
|
159
|
+
@instance.expects(:obfuscate_hostname?).with(host).returns(true)
|
160
|
+
@instance.expects(:fact_value).with(host, 'insights_client::obfuscated_hostname').returns('[]')
|
161
|
+
|
162
|
+
result = @instance.fqdn(host)
|
163
|
+
|
164
|
+
expected = "#{Digest::SHA1.hexdigest('test.example.com')}.example.com"
|
165
|
+
assert_equal expected, result
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
describe 'obfuscate_ips?' do
|
170
|
+
test 'returns true when global setting is enabled' do
|
171
|
+
Setting.expects(:[]).with(:obfuscate_inventory_ips).returns(true)
|
172
|
+
host = mock('host')
|
173
|
+
|
174
|
+
result = @instance.obfuscate_ips?(host)
|
175
|
+
|
176
|
+
assert result
|
177
|
+
end
|
178
|
+
|
179
|
+
test 'returns false when global setting is disabled and no host-specific settings' do
|
180
|
+
Setting.expects(:[]).with(:obfuscate_inventory_ips).returns(false)
|
181
|
+
host = mock('host')
|
182
|
+
@instance.expects(:fact_value).with(host, 'insights_client::obfuscate_ipv4_enabled').returns(nil)
|
183
|
+
@instance.expects(:fact_value).with(host, 'insights_client::obfuscate_ipv6_enabled').returns(nil)
|
184
|
+
|
185
|
+
result = @instance.obfuscate_ips?(host)
|
186
|
+
|
187
|
+
refute result
|
188
|
+
end
|
189
|
+
|
190
|
+
test 'returns true when host-specific IPv4 setting is enabled' do
|
191
|
+
Setting.expects(:[]).with(:obfuscate_inventory_ips).returns(false)
|
192
|
+
host = mock('host')
|
193
|
+
@instance.expects(:fact_value).with(host, 'insights_client::obfuscate_ipv4_enabled').returns('true')
|
194
|
+
@instance.expects(:fact_value).with(host, 'insights_client::obfuscate_ipv6_enabled').returns(nil)
|
195
|
+
|
196
|
+
result = @instance.obfuscate_ips?(host)
|
197
|
+
|
198
|
+
assert result
|
199
|
+
end
|
200
|
+
|
201
|
+
test 'returns true when host-specific IPv6 setting is enabled' do
|
202
|
+
Setting.expects(:[]).with(:obfuscate_inventory_ips).returns(false)
|
203
|
+
host = mock('host')
|
204
|
+
@instance.expects(:fact_value).with(host, 'insights_client::obfuscate_ipv4_enabled').returns(nil)
|
205
|
+
@instance.expects(:fact_value).with(host, 'insights_client::obfuscate_ipv6_enabled').returns('true')
|
206
|
+
|
207
|
+
result = @instance.obfuscate_ips?(host)
|
208
|
+
|
209
|
+
assert result
|
210
|
+
end
|
211
|
+
|
212
|
+
test 'returns true when both IPv4 and IPv6 settings are enabled' do
|
213
|
+
Setting.expects(:[]).with(:obfuscate_inventory_ips).returns(false)
|
214
|
+
host = mock('host')
|
215
|
+
@instance.expects(:fact_value).with(host, 'insights_client::obfuscate_ipv4_enabled').returns('true')
|
216
|
+
@instance.expects(:fact_value).with(host, 'insights_client::obfuscate_ipv6_enabled').returns('true')
|
217
|
+
|
218
|
+
result = @instance.obfuscate_ips?(host)
|
219
|
+
|
220
|
+
assert result
|
221
|
+
end
|
222
|
+
|
223
|
+
test 'returns false when both IPv4 and IPv6 settings are disabled' do
|
224
|
+
Setting.expects(:[]).with(:obfuscate_inventory_ips).returns(false)
|
225
|
+
host = mock('host')
|
226
|
+
@instance.expects(:fact_value).with(host, 'insights_client::obfuscate_ipv4_enabled').returns('false')
|
227
|
+
@instance.expects(:fact_value).with(host, 'insights_client::obfuscate_ipv6_enabled').returns('false')
|
228
|
+
|
229
|
+
result = @instance.obfuscate_ips?(host)
|
230
|
+
|
231
|
+
refute result
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
describe 'obfuscate_ip' do
|
236
|
+
test 'generates first IP when no existing obfuscated IPs' do
|
237
|
+
ips_dict = {}
|
238
|
+
|
239
|
+
result = @instance.obfuscate_ip('192.168.1.1', ips_dict)
|
240
|
+
|
241
|
+
assert_equal '10.230.230.1', result
|
242
|
+
end
|
243
|
+
|
244
|
+
test 'generates next sequential IP when existing obfuscated IPs present' do
|
245
|
+
ips_dict = { '192.168.1.1' => '10.230.230.5', '192.168.1.2' => '10.230.230.10' }
|
246
|
+
|
247
|
+
result = @instance.obfuscate_ip('192.168.1.3', ips_dict)
|
248
|
+
|
249
|
+
assert_equal '10.230.230.11', result
|
250
|
+
end
|
251
|
+
|
252
|
+
test 'handles mixed IP ranges correctly' do
|
253
|
+
ips_dict = { '192.168.1.1' => '10.230.230.255', '192.168.1.2' => '10.230.230.1' }
|
254
|
+
|
255
|
+
result = @instance.obfuscate_ip('192.168.1.3', ips_dict)
|
256
|
+
|
257
|
+
assert_equal '10.230.231.0', result
|
258
|
+
end
|
259
|
+
|
260
|
+
test 'generates valid IP addresses' do
|
261
|
+
ips_dict = {}
|
262
|
+
|
263
|
+
result = @instance.obfuscate_ip('any.ip.address', ips_dict)
|
264
|
+
|
265
|
+
assert_match(/\A\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\z/, result)
|
266
|
+
assert_nothing_raised { IPAddr.new(result) }
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
describe 'obfuscated_ips' do
|
271
|
+
test 'handles invalid JSON in insights_client fact gracefully' do
|
272
|
+
host = mock('host')
|
273
|
+
@instance.expects(:fact_value).with(host, 'insights_client::obfuscated_ipv4').returns('invalid json')
|
274
|
+
|
275
|
+
result = @instance.obfuscated_ips(host)
|
276
|
+
|
277
|
+
assert_equal '10.230.230.1', result['192.168.1.1']
|
278
|
+
end
|
279
|
+
|
280
|
+
test 'handles empty insights_client fact' do
|
281
|
+
host = mock('host')
|
282
|
+
@instance.expects(:fact_value).with(host, 'insights_client::obfuscated_ipv4').returns('[]')
|
283
|
+
|
284
|
+
result = @instance.obfuscated_ips(host)
|
285
|
+
|
286
|
+
assert_equal '10.230.230.1', result['192.168.1.1']
|
287
|
+
end
|
288
|
+
|
289
|
+
test 'preserves existing obfuscated IPs and generates new ones' do
|
290
|
+
host = mock('host')
|
291
|
+
@instance.expects(:fact_value).with(host, 'insights_client::obfuscated_ipv4').returns(
|
292
|
+
'[{"original": "192.168.1.1", "obfuscated": "10.230.230.5"}]'
|
293
|
+
)
|
294
|
+
|
295
|
+
result = @instance.obfuscated_ips(host)
|
296
|
+
|
297
|
+
assert_equal '10.230.230.5', result['192.168.1.1']
|
298
|
+
assert_equal '10.230.230.6', result['192.168.1.2']
|
299
|
+
end
|
300
|
+
|
301
|
+
test 'default_proc generates unique sequential IPs' do
|
302
|
+
host = mock('host')
|
303
|
+
@instance.expects(:fact_value).with(host, 'insights_client::obfuscated_ipv4').returns(nil)
|
304
|
+
|
305
|
+
result = @instance.obfuscated_ips(host)
|
306
|
+
|
307
|
+
ip1 = result['192.168.1.1']
|
308
|
+
ip2 = result['192.168.1.2']
|
309
|
+
ip3 = result['192.168.1.3']
|
310
|
+
|
311
|
+
assert_equal '10.230.230.1', ip1
|
312
|
+
assert_equal '10.230.230.2', ip2
|
313
|
+
assert_equal '10.230.230.3', ip3
|
314
|
+
end
|
315
|
+
end
|
51
316
|
end
|
@@ -3,6 +3,7 @@ require 'puma/null_io'
|
|
3
3
|
|
4
4
|
class CloudRequestForwarderTest < ActiveSupport::TestCase
|
5
5
|
include MockCerts
|
6
|
+
include KatelloCVEHelper
|
6
7
|
|
7
8
|
setup do
|
8
9
|
@forwarder = ::ForemanRhCloud::CloudRequestForwarder.new
|
@@ -10,6 +11,22 @@ class CloudRequestForwarderTest < ActiveSupport::TestCase
|
|
10
11
|
ForemanRhCloud.stubs(:base_url).returns('https://cloud.example.com')
|
11
12
|
ForemanRhCloud.stubs(:cert_base_url).returns('https://cert.cloud.example.com')
|
12
13
|
ForemanRhCloud.stubs(:legacy_insights_url).returns('https://cert-api.access.example.com')
|
14
|
+
|
15
|
+
UpstreamOnlySettingsTestHelper.set_if_available('allow_multiple_content_views')
|
16
|
+
env = FactoryBot.create(:katello_k_t_environment)
|
17
|
+
env2 = FactoryBot.create(:katello_k_t_environment, organization: env.organization)
|
18
|
+
|
19
|
+
@host = FactoryBot.create(
|
20
|
+
:host,
|
21
|
+
:with_subscription,
|
22
|
+
:with_content,
|
23
|
+
:with_hostgroup,
|
24
|
+
:with_parameter,
|
25
|
+
content_view_environments: [make_cve(lifecycle_environment: env), make_cve(lifecycle_environment: env2)],
|
26
|
+
organization: env.organization
|
27
|
+
)
|
28
|
+
|
29
|
+
@host.subscription_facet.pools << FactoryBot.create(:katello_pool, account_number: '5678', cp_id: 1)
|
13
30
|
end
|
14
31
|
|
15
32
|
test 'should prepare correct cloud url' do
|
@@ -150,7 +167,7 @@ class CloudRequestForwarderTest < ActiveSupport::TestCase
|
|
150
167
|
'action_dispatch.request.query_parameters' => params
|
151
168
|
)
|
152
169
|
|
153
|
-
actual = @forwarder.prepare_request_opts(req, 'TEST PAYLOAD', params, generate_certs_hash)
|
170
|
+
actual = @forwarder.prepare_request_opts(req, 'TEST PAYLOAD', params, generate_certs_hash, @host)
|
154
171
|
|
155
172
|
assert_match /foo/, actual[:headers][:user_agent]
|
156
173
|
assert_match /bar/, actual[:headers][:user_agent]
|
@@ -175,7 +192,7 @@ class CloudRequestForwarderTest < ActiveSupport::TestCase
|
|
175
192
|
'action_dispatch.request.query_parameters' => params
|
176
193
|
)
|
177
194
|
|
178
|
-
actual = @forwarder.prepare_request_opts(req, 'TEST PAYLOAD', params, generate_certs_hash)
|
195
|
+
actual = @forwarder.prepare_request_opts(req, 'TEST PAYLOAD', params, generate_certs_hash, @host)
|
179
196
|
|
180
197
|
assert_match /text\/html/, actual[:headers][:content_type]
|
181
198
|
end
|
@@ -64,9 +64,10 @@ class SliceGeneratorTest < ActiveSupport::TestCase
|
|
64
64
|
'dmi::system::product_name',
|
65
65
|
'dmi::chassis::asset_tag',
|
66
66
|
'insights_client::obfuscate_hostname_enabled',
|
67
|
+
'insights_client::obfuscated_hostname',
|
68
|
+
'insights_client::obfuscate_ipv4_enabled',
|
69
|
+
'insights_client::obfuscated_ipv4',
|
67
70
|
'insights_client::hostname',
|
68
|
-
'insights_client::obfuscate_ip_enabled',
|
69
|
-
'insights_client::ips',
|
70
71
|
'insights_id',
|
71
72
|
]
|
72
73
|
end
|
@@ -418,14 +419,38 @@ class SliceGeneratorTest < ActiveSupport::TestCase
|
|
418
419
|
assert_equal 1, generator.hosts_count
|
419
420
|
end
|
420
421
|
|
421
|
-
test '
|
422
|
+
test 'does not obfuscate fqdn when insights_client obfuscate_hostname_enabled fact is missing and obfuscate_inventory_hostnames setting is false' do
|
423
|
+
# Create a host and obfuscated_hostname fact, but do NOT create the obfuscate_hostname_enabled fact
|
424
|
+
obfuscated_hostname_data = [
|
425
|
+
{ 'original' => @host.fqdn, 'obfuscated' => '0dd449d0a027.example.com' },
|
426
|
+
]
|
427
|
+
obfuscated_hostname_value = JSON.generate(obfuscated_hostname_data)
|
428
|
+
FactoryBot.create(:fact_value,
|
429
|
+
fact_name: fact_names['insights_client::obfuscated_hostname'],
|
430
|
+
value: obfuscated_hostname_value,
|
431
|
+
host: @host)
|
432
|
+
# Do NOT create the 'insights_client::obfuscate_hostname_enabled' fact
|
433
|
+
|
434
|
+
batch = Host.where(id: @host.id).in_batches.first
|
435
|
+
generator = create_generator(batch)
|
436
|
+
|
437
|
+
json_str = generator.render
|
438
|
+
actual = JSON.parse(json_str.join("\n"))
|
439
|
+
|
440
|
+
assert_not_nil(actual_host = actual['hosts'].first)
|
441
|
+
assert_equal @host.fqdn, actual_host['fqdn'], "FQDN should not be obfuscated when obfuscate_hostname_enabled is missing and setting is false"
|
442
|
+
assert_not_nil(actual_facts = actual_host['facts'].first['facts'])
|
443
|
+
assert_not_equal true, actual_facts['is_hostname_obfuscated']
|
444
|
+
end
|
445
|
+
|
446
|
+
test 'generates obfuscated ip_address fields when insights-client facts are present' do
|
422
447
|
nic = FactoryBot.build(:nic_managed)
|
423
448
|
@host.interfaces << nic
|
424
449
|
|
425
|
-
FactoryBot.create(:fact_value, fact_name: fact_names['insights_client::
|
450
|
+
FactoryBot.create(:fact_value, fact_name: fact_names['insights_client::obfuscate_ipv4_enabled'], value: 'true', host: @host)
|
426
451
|
FactoryBot.create(
|
427
452
|
:fact_value,
|
428
|
-
fact_name: fact_names['insights_client::
|
453
|
+
fact_name: fact_names['insights_client::obfuscated_ipv4'],
|
429
454
|
value: "[{\"obfuscated\": \"10.230.230.100\", \"original\": \"#{nic.ip}\"}]",
|
430
455
|
host: @host
|
431
456
|
)
|
@@ -448,9 +473,17 @@ class SliceGeneratorTest < ActiveSupport::TestCase
|
|
448
473
|
assert_equal 1, generator.hosts_count
|
449
474
|
end
|
450
475
|
|
451
|
-
test 'obfuscates fqdn when
|
476
|
+
test 'obfuscates fqdn when insights-client facts are present' do
|
477
|
+
obfuscated_hostname_data = [
|
478
|
+
{ 'original' => @host.fqdn, 'obfuscated' => '0dd449d0a027.example.com' },
|
479
|
+
{ 'original' => 'satellite.theforeman.org', 'obfuscated' => 'host2.example.com' },
|
480
|
+
]
|
481
|
+
obfuscated_hostname_value = JSON.generate(obfuscated_hostname_data)
|
482
|
+
FactoryBot.create(:fact_value,
|
483
|
+
fact_name: fact_names['insights_client::obfuscated_hostname'],
|
484
|
+
value: obfuscated_hostname_value,
|
485
|
+
host: @host)
|
452
486
|
FactoryBot.create(:fact_value, fact_name: fact_names['insights_client::obfuscate_hostname_enabled'], value: 'true', host: @host)
|
453
|
-
FactoryBot.create(:fact_value, fact_name: fact_names['insights_client::hostname'], value: 'obfuscated_name', host: @host)
|
454
487
|
|
455
488
|
batch = Host.where(id: @host.id).in_batches.first
|
456
489
|
generator = create_generator(batch)
|
@@ -460,7 +493,7 @@ class SliceGeneratorTest < ActiveSupport::TestCase
|
|
460
493
|
|
461
494
|
assert_equal '00000000-0000-0000-0000-000000000000', actual['report_slice_id']
|
462
495
|
assert_not_nil(actual_host = actual['hosts'].first)
|
463
|
-
assert_equal '
|
496
|
+
assert_equal obfuscated_hostname_data.first['obfuscated'], actual_host['fqdn']
|
464
497
|
assert_equal '1234', actual_host['account']
|
465
498
|
assert_not_nil(actual_facts = actual_host['facts'].first['facts'])
|
466
499
|
assert_equal true, actual_facts['is_hostname_obfuscated']
|
@@ -487,9 +520,35 @@ class SliceGeneratorTest < ActiveSupport::TestCase
|
|
487
520
|
assert_equal 1, generator.hosts_count
|
488
521
|
end
|
489
522
|
|
490
|
-
test '
|
523
|
+
test 'obfuscates host fqdn with insights-client when setting set' do
|
524
|
+
Setting[:obfuscate_inventory_hostnames] = true
|
525
|
+
FactoryBot.create(:fact_value, fact_name: fact_names['insights_client::hostname'], value: @host.fqdn, host: @host)
|
526
|
+
|
527
|
+
batch = Host.where(id: @host.id).in_batches.first
|
528
|
+
generator = create_generator(batch)
|
529
|
+
|
530
|
+
json_str = generator.render
|
531
|
+
actual = JSON.parse(json_str.join("\n"))
|
532
|
+
|
533
|
+
obfuscated_fqdn = Digest::SHA1.hexdigest(@host.fqdn) + '.example.com'
|
534
|
+
|
535
|
+
assert_equal '00000000-0000-0000-0000-000000000000', actual['report_slice_id']
|
536
|
+
assert_not_nil(actual_host = actual['hosts'].first)
|
537
|
+
assert_equal obfuscated_fqdn, actual_host['fqdn']
|
538
|
+
assert_equal '1234', actual_host['account']
|
539
|
+
assert_not_nil(actual_facts = actual_host['facts'].first['facts'])
|
540
|
+
assert_equal true, actual_facts['is_hostname_obfuscated']
|
541
|
+
assert_equal 1, generator.hosts_count
|
542
|
+
end
|
543
|
+
|
544
|
+
test 'does not obfuscate fqdn when host fact from insights-client has a value of false' do
|
545
|
+
obfuscated_hostname_data = [
|
546
|
+
{ 'original' => @host.fqdn, 'obfuscated' => '0dd449d0a027.example.com' },
|
547
|
+
{ 'original' => 'satellite.theforeman.org', 'obfuscated' => 'host2.example.com' },
|
548
|
+
]
|
549
|
+
obfuscated_hostname_value = JSON.generate(obfuscated_hostname_data)
|
491
550
|
FactoryBot.create(:fact_value, fact_name: fact_names['insights_client::obfuscate_hostname_enabled'], value: 'false', host: @host)
|
492
|
-
FactoryBot.create(:fact_value, fact_name: fact_names['insights_client::
|
551
|
+
FactoryBot.create(:fact_value, fact_name: fact_names['insights_client::obfuscated_hostname'], value: obfuscated_hostname_value, host: @host)
|
493
552
|
|
494
553
|
batch = Host.where(id: @host.id).in_batches.first
|
495
554
|
generator = create_generator(batch)
|
@@ -0,0 +1,41 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import { translate as __ } from 'foremanReact/common/I18n';
|
3
|
+
import { propsToCamelCase } from 'foremanReact/common/helpers';
|
4
|
+
|
5
|
+
const RecommendationsCell = hostDetails => {
|
6
|
+
const insightsAttributes = propsToCamelCase(
|
7
|
+
// eslint-disable-next-line camelcase
|
8
|
+
hostDetails?.insights_attributes ?? {}
|
9
|
+
);
|
10
|
+
// Local insights advisor
|
11
|
+
if (insightsAttributes.useLocalAdvisorEngine) {
|
12
|
+
// TODO: Replace this placeholder with the actual local advisor integration
|
13
|
+
return <span>Local advisor placeholder</span>;
|
14
|
+
}
|
15
|
+
|
16
|
+
// Hosted insights advisor
|
17
|
+
const { insightsHitsCount: hitsCount } = insightsAttributes;
|
18
|
+
if (hitsCount === undefined || hitsCount === null) return '—';
|
19
|
+
const hostname = hostDetails?.name;
|
20
|
+
const encodedHostname = encodeURIComponent(hostname);
|
21
|
+
const hitsUrl = `/foreman_rh_cloud/insights_cloud?search=hostname+%3D+${encodedHostname}`;
|
22
|
+
return <a href={hitsUrl}>{hitsCount}</a>;
|
23
|
+
};
|
24
|
+
|
25
|
+
const hostsIndexColumnExtensions = [
|
26
|
+
{
|
27
|
+
columnName: 'insights_recommendations_count',
|
28
|
+
title: __('Recommendations'),
|
29
|
+
wrapper: RecommendationsCell,
|
30
|
+
weight: 1500,
|
31
|
+
isSorted: true,
|
32
|
+
},
|
33
|
+
];
|
34
|
+
|
35
|
+
hostsIndexColumnExtensions.forEach(column => {
|
36
|
+
column.tableName = 'hosts';
|
37
|
+
column.categoryName = 'Insights';
|
38
|
+
column.categoryKey = 'insights';
|
39
|
+
});
|
40
|
+
|
41
|
+
export default hostsIndexColumnExtensions;
|
data/webpack/global_index.js
CHANGED
@@ -1,7 +1,10 @@
|
|
1
|
+
import { registerColumns } from 'foremanReact/components/HostsIndex/Columns/core';
|
1
2
|
import { registerReducers } from './ForemanRhCloudReducers';
|
2
3
|
import { registerFills } from './ForemanRhCloudFills';
|
3
4
|
import { registerRoutes } from './ForemanRhCloudPages';
|
5
|
+
import hostsIndexColumnExtensions from './ForemanColumnExtensions/index';
|
4
6
|
|
5
7
|
registerReducers();
|
6
8
|
registerFills();
|
7
9
|
registerRoutes();
|
10
|
+
registerColumns(hostsIndexColumnExtensions);
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: foreman_rh_cloud
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 12.1.
|
4
|
+
version: 12.1.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Foreman Red Hat Cloud team
|
@@ -100,6 +100,7 @@ files:
|
|
100
100
|
- app/controllers/api/v2/rh_cloud/inventory_controller.rb
|
101
101
|
- app/controllers/concerns/insights_cloud/candlepin_cache.rb
|
102
102
|
- app/controllers/concerns/insights_cloud/client_authentication.rb
|
103
|
+
- app/controllers/concerns/insights_cloud/package_profile_upload_extensions.rb
|
103
104
|
- app/controllers/concerns/inventory_upload/report_actions.rb
|
104
105
|
- app/controllers/concerns/inventory_upload/task_actions.rb
|
105
106
|
- app/controllers/foreman_inventory_upload/accounts_controller.rb
|
@@ -284,6 +285,7 @@ files:
|
|
284
285
|
- webpack/CVEsHostDetailsTab/CVEsHostDetailsTab.js
|
285
286
|
- webpack/CVEsHostDetailsTab/__tests__/CVEsHostDetailsTab.test.js
|
286
287
|
- webpack/CVEsHostDetailsTab/index.js
|
288
|
+
- webpack/ForemanColumnExtensions/index.js
|
287
289
|
- webpack/ForemanInventoryUpload/Components/AccountList/AccountList.fixtures.js
|
288
290
|
- webpack/ForemanInventoryUpload/Components/AccountList/AccountList.js
|
289
291
|
- webpack/ForemanInventoryUpload/Components/AccountList/AccountList.stories.js
|