foreman_rh_cloud 14.5.0 → 14.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b3d41bcf3b5de41dab8c50751aecf9374fcb0bce44f1e0896011161b25ace53e
4
- data.tar.gz: 8eb6a78729500fcb4ae06e8a35e452a0ed7a57e8dbc9f1d8ddf46b8c2a341016
3
+ metadata.gz: 6c5686d5268a4028cc883b95daa1948748a56a41cb54495668a4170cd1fcaab9
4
+ data.tar.gz: 533e44e09c87944392c120d57cc34e9244fbe3cf176dddb6c8d7ab7334ce4f92
5
5
  SHA512:
6
- metadata.gz: d42639d1e4cb269096eb8cabe57a4337ee9c3e04e6339d2fdf8ac79ccf87188d60293820b5c6c61d79d03ecf40b5c042c2da8e81bb70251dda030cc24c74d1e2
7
- data.tar.gz: c709b8044deff700aa3c541c6c21a9afdec88a449366785251057c64f1d85f73b9080b4391aca99d7564657509e3ee53c03de90f9f6d98dc9239d9de9a29f053
6
+ metadata.gz: ca9d76abf073cb2f31f74822d45c40d18f6f735f2d6251d67c3ebae1bec9d26c33fe6c9098e8bb6f1b21f922003eaecbfe0da7249111e28ea4cd0f04e0d25138
7
+ data.tar.gz: cbe8078e2ef86cde32ad7feef2c0dd19850de71cefec7aa61939d71605d749e0ad18225492c160b5f5d57ba15bea691f0af0a5978e5300979e4858bc959b0e72
@@ -11,8 +11,9 @@ module ForemanRhCloud
11
11
 
12
12
  def execute_cloud_request(params)
13
13
  organization = params.delete(:organization)
14
+ force_cloud = params[:force_cloud]
14
15
  # Cache the value of with_iop_smart_proxy? to avoid multiple calls to the database
15
- with_iop_smart_proxy = ForemanRhCloud.with_iop_smart_proxy?
16
+ with_iop_smart_proxy = ForemanRhCloud.with_iop_smart_proxy? && !force_cloud
16
17
  certs = with_iop_smart_proxy ? foreman_certificate : candlepin_id_cert(organization)
17
18
  default_params = {
18
19
  ssl_client_cert: OpenSSL::X509::Certificate.new(certs[:cert]),
@@ -3,12 +3,13 @@ module ForemanRhCloud
3
3
  extend ActiveSupport::Concern
4
4
 
5
5
  def execute_cloud_request(params)
6
+ force_cloud = params.delete(:force_cloud)
6
7
  final_params = {
7
8
  verify_ssl: ForemanRhCloud.verify_ssl_method,
8
9
  proxy: ForemanRhCloud.transformed_http_proxy_string,
9
10
  }.deep_merge(params)
10
11
 
11
- if ForemanRhCloud.with_iop_smart_proxy?
12
+ if ForemanRhCloud.with_iop_smart_proxy? && !force_cloud
12
13
  final_params[:ssl_ca_file] ||= ForemanRhCloud.ca_cert
13
14
  end
14
15
 
@@ -36,8 +36,9 @@ module ForemanRhCloud
36
36
  }
37
37
  requested_url = original_request.original_fullpath.end_with?('/') ? original_request.path + '/' : original_request.path
38
38
  params = path_params(requested_url)
39
+ force_cloud = params[:force_cloud]
39
40
 
40
- if ForemanRhCloud.with_iop_smart_proxy?
41
+ if ForemanRhCloud.with_iop_smart_proxy? && !force_cloud
41
42
  params[:ssl_ca_file] = ForemanRhCloud.ca_cert
42
43
  end
43
44
 
@@ -70,10 +71,13 @@ module ForemanRhCloud
70
71
 
71
72
  def path_params(request_path)
72
73
  case request_path
73
- when lightspeed?
74
+ when lightspeed_cla?
75
+ force_cloud = ForemanRhCloud.with_iop_smart_proxy? && Setting[:force_cla_connection]
74
76
  {
75
- url: ForemanRhCloud.cert_base_url + request_path,
76
- }
77
+ url: ForemanRhCloud.cloud_cert_base_url + request_path,
78
+ force_cloud: force_cloud,
79
+ proxy: (force_cloud ? ForemanRhCloud.transformed_cloud_http_proxy_string : nil),
80
+ }.compact
77
81
  when platform_request?
78
82
  {
79
83
  url: ForemanRhCloud.cert_base_url + request_path.sub('/redhat_access/r/insights/platform', '/api'),
@@ -104,7 +108,7 @@ module ForemanRhCloud
104
108
  "for=\"_#{host.subscription_facet.uuid}\""
105
109
  end
106
110
 
107
- def lightspeed?
111
+ def lightspeed_cla?
108
112
  ->(request_path) { request_path.include? '/lightspeed' }
109
113
  end
110
114
 
@@ -22,6 +22,7 @@ module ForemanRhCloud
22
22
  setting('include_parameter_tags', type: :boolean, description: N_('Should import include parameter tags from Foreman? Ignored when using local Insights.'), default: false, full_name: N_('Include parameters in insights-client reports'))
23
23
  setting('rhc_instance_id', type: :string, description: N_('RHC daemon id. Ignored when using local Insights.'), default: nil, full_name: N_('ID of the RHC(Yggdrasil) daemon'))
24
24
  setting('insights_minimal_data_collection', type: :boolean, default: false, full_name: N_('Minimal data collection'), description: N_('Only include the minimum required data in inventory reports for uploading to Red Hat cloud. When this is true, installed packages are excluded from the report regardless of the exclude_installed_packages setting, and host names and IPv4 addresses are excluded from the report regardless of obfuscation settings. Ignored when using local Insights.'))
25
+ setting('force_cla_connection', type: :boolean, default: false, full_name: N_('Force RHEL Lightspeed CLA connection'), description: N_('Forward requests to Lightspeed CLA even when in IoP mode. Ignored when IoP mode is off.'))
25
26
  end
26
27
  end
27
28
 
@@ -1,3 +1,3 @@
1
1
  module ForemanRhCloud
2
- VERSION = '14.5.0'.freeze
2
+ VERSION = '14.6.0'.freeze
3
3
  end
@@ -21,6 +21,14 @@ module ForemanRhCloud
21
21
  env_or_on_premise_url('SATELLITE_CERT_RH_CLOUD_URL') || 'https://cert.cloud.redhat.com'
22
22
  end
23
23
 
24
+ # Lightspeed/CLA is cloud-only. Use the cloud URL only when the admin
25
+ # enables force_cla_connection; otherwise follow cert_base_url (IoP when IoP is on).
26
+ def self.cloud_cert_base_url
27
+ return cert_base_url unless Setting[:force_cla_connection]
28
+
29
+ ENV['SATELLITE_CERT_RH_CLOUD_URL'] || 'https://cert.cloud.redhat.com'
30
+ end
31
+
24
32
  def self.legacy_insights_url
25
33
  env_or_on_premise_url('SATELLITE_LEGACY_INSIGHTS_URL') || 'https://cert-api.access.redhat.com'
26
34
  end
@@ -48,11 +56,19 @@ module ForemanRhCloud
48
56
  def self.proxy_string
49
57
  return '' if ForemanRhCloud.with_iop_smart_proxy?
50
58
 
59
+ cloud_proxy_string
60
+ end
61
+
62
+ def self.cloud_proxy_string
51
63
  HttpProxy.default_global_content_proxy&.full_url ||
52
64
  ForemanRhCloud.global_foreman_proxy ||
53
65
  ''
54
66
  end
55
67
 
68
+ def self.transformed_cloud_http_proxy_string
69
+ transform_scheme(fix_port(cloud_proxy_string))
70
+ end
71
+
56
72
  def self.fix_port(uri_string)
57
73
  return '' if uri_string.empty?
58
74
 
data/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "foreman_rh_cloud",
3
- "version": "14.5.0",
3
+ "version": "14.6.0",
4
4
  "description": "Inventory Upload =============",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -101,6 +101,22 @@ class ForemanRhCloudIopMetadataTest < ActiveSupport::TestCase
101
101
  assert_equal 'https://iop.example.com', ForemanRhCloud.cert_base_url
102
102
  end
103
103
 
104
+ test 'cloud_cert_base_url returns IoP URL when IoP exists and force_cla_connection is off' do
105
+ create_iop_proxy
106
+ Setting[:force_cla_connection] = false
107
+
108
+ assert_equal 'https://iop.example.com', ForemanRhCloud.cloud_cert_base_url
109
+ end
110
+
111
+ test 'cloud_cert_base_url returns cloud URL when IoP exists and force_cla_connection is on' do
112
+ create_iop_proxy
113
+ Setting[:force_cla_connection] = true
114
+
115
+ assert_equal 'https://cert.cloud.redhat.com', ForemanRhCloud.cloud_cert_base_url
116
+ ensure
117
+ Setting[:force_cla_connection] = false
118
+ end
119
+
104
120
  test 'legacy_insights_url returns IoP URL when IoP smart proxy exists' do
105
121
  create_iop_proxy
106
122
 
@@ -153,6 +169,17 @@ class ForemanRhCloudIopMetadataTest < ActiveSupport::TestCase
153
169
  ENV.delete('SATELLITE_CERT_RH_CLOUD_URL')
154
170
  end
155
171
 
172
+ test 'cloud_cert_base_url uses ENV var when force_cla_connection is on' do
173
+ ENV['SATELLITE_CERT_RH_CLOUD_URL'] = 'https://env-cert.cloud.test'
174
+ Setting[:force_cla_connection] = true
175
+ create_iop_proxy
176
+
177
+ assert_equal 'https://env-cert.cloud.test', ForemanRhCloud.cloud_cert_base_url
178
+ ensure
179
+ ENV.delete('SATELLITE_CERT_RH_CLOUD_URL')
180
+ Setting[:force_cla_connection] = false
181
+ end
182
+
156
183
  test 'legacy_insights_url prefers IoP URL over ENV var' do
157
184
  ENV['SATELLITE_LEGACY_INSIGHTS_URL'] = 'https://env-legacy.insights.test'
158
185
 
@@ -10,7 +10,9 @@ class CloudRequestForwarderTest < ActiveSupport::TestCase
10
10
 
11
11
  ForemanRhCloud.stubs(:base_url).returns('https://cloud.example.com')
12
12
  ForemanRhCloud.stubs(:cert_base_url).returns('https://cert.cloud.example.com')
13
+ ForemanRhCloud.stubs(:cloud_cert_base_url).returns('https://cert.cloud.example.com')
13
14
  ForemanRhCloud.stubs(:legacy_insights_url).returns('https://cert-api.access.example.com')
15
+ ForemanRhCloud.stubs(:transformed_cloud_http_proxy_string).returns(nil)
14
16
 
15
17
  UpstreamOnlySettingsTestHelper.set_if_available('allow_multiple_content_views')
16
18
  env = FactoryBot.create(:katello_k_t_environment)
@@ -34,6 +36,7 @@ class CloudRequestForwarderTest < ActiveSupport::TestCase
34
36
 
35
37
  test 'should prepare correct cloud url' do
36
38
  paths = {
39
+ "/api/lightspeed/v1/query" => "https://cert.cloud.example.com/api/lightspeed/v1/query",
37
40
  "/redhat_access/r/insights/platform/module-update-router/v1/channel?module=insights-core" => "https://cert.cloud.example.com/api/module-update-router/v1/channel?module=insights-core",
38
41
  "/redhat_access/r/insights/v1/static/release/insights-core.egg" => "https://cert-api.access.example.com/r/insights/v1/static/release/insights-core.egg",
39
42
  "/redhat_access/r/insights/v1/static/uploader.v2.json" => "https://cert-api.access.example.com/r/insights/v1/static/uploader.v2.json",
@@ -50,6 +53,31 @@ class CloudRequestForwarderTest < ActiveSupport::TestCase
50
53
  end
51
54
  end
52
55
 
56
+ test 'does not force CLA to cloud when IoP is enabled and setting is off' do
57
+ ForemanRhCloud.stubs(:with_iop_smart_proxy?).returns(true)
58
+ ForemanRhCloud.stubs(:cert_base_url).returns('https://iop.example.com')
59
+ ForemanRhCloud.stubs(:cloud_cert_base_url).returns('https://iop.example.com')
60
+ Setting.stubs(:[]).with(:force_cla_connection).returns(false)
61
+
62
+ actual = @forwarder.path_params('/api/lightspeed/v1/query')
63
+
64
+ assert_equal 'https://iop.example.com/api/lightspeed/v1/query', actual[:url]
65
+ refute actual[:force_cloud]
66
+ end
67
+
68
+ test 'forwards CLA to cloud when IoP is enabled and force_cla_connection is on' do
69
+ ForemanRhCloud.stubs(:with_iop_smart_proxy?).returns(true)
70
+ ForemanRhCloud.stubs(:cert_base_url).returns('https://iop.example.com')
71
+ ForemanRhCloud.stubs(:cloud_cert_base_url).returns('https://cert.cloud.example.com')
72
+ ForemanRhCloud.stubs(:transformed_cloud_http_proxy_string).returns(nil)
73
+ Setting.stubs(:[]).with(:force_cla_connection).returns(true)
74
+
75
+ actual = @forwarder.path_params('/api/lightspeed/v1/query')
76
+
77
+ assert_equal 'https://cert.cloud.example.com/api/lightspeed/v1/query', actual[:url]
78
+ assert actual[:force_cloud]
79
+ end
80
+
53
81
  test 'should forward payload from request parameters' do
54
82
  params = { 'pumpkin' => 'pie' }
55
83
  req = ActionDispatch::Request.new(
@@ -1,82 +1,83 @@
1
1
  import React from 'react';
2
- import { shallow } from '@theforeman/test';
2
+ import { render, screen } from '@testing-library/react';
3
+ import { MemoryRouter } from 'react-router-dom';
4
+ import '@testing-library/jest-dom';
3
5
  import Toast from '../Toast';
4
6
 
7
+ jest.mock('foremanReact/common/I18n', () => ({
8
+ translate: jest.fn(str => str),
9
+ }));
10
+
11
+ const renderToast = (props = {}) =>
12
+ render(
13
+ <MemoryRouter>
14
+ <Toast syncHosts={5} disconnectHosts={3} {...props} />
15
+ </MemoryRouter>
16
+ );
17
+
18
+ const omittedText =
19
+ 'Excluded from upload to console.redhat.com Inventory service because host_registration_insights_inventory parameter value is false:';
20
+
5
21
  describe('Toast', () => {
6
22
  it('renders with all three status counts including user_omitted', () => {
7
- const wrapper = shallow(
8
- <Toast syncHosts={5} disconnectHosts={3} userOmittedHosts={2} />
9
- );
10
-
11
- const links = wrapper.find('HostsWithStatusLink');
12
- expect(links).toHaveLength(3);
23
+ renderToast({ userOmittedHosts: 2 });
13
24
 
14
- // Check the children (numbers) of each link
15
- expect(
16
- links
17
- .at(0)
18
- .children()
19
- .text()
20
- ).toBe('5');
21
- expect(
22
- links
23
- .at(1)
24
- .children()
25
- .text()
26
- ).toBe('3');
27
- expect(
28
- links
29
- .at(2)
30
- .children()
31
- .text()
32
- ).toBe('2');
25
+ expect(screen.getByRole('link', { name: '10' })).toHaveAttribute(
26
+ 'href',
27
+ '/new/hosts?search=set%3F+subscription_uuid&page=1'
28
+ );
29
+ expect(screen.getByRole('link', { name: '5' })).toHaveAttribute(
30
+ 'href',
31
+ '/new/hosts?search=insights_inventory_sync_status+%3D+sync&page=1'
32
+ );
33
+ expect(screen.getByRole('link', { name: '3' })).toHaveAttribute(
34
+ 'href',
35
+ '/new/hosts?search=insights_inventory_sync_status+%3D+disconnect&page=1'
36
+ );
37
+ expect(screen.getByRole('link', { name: '2' })).toHaveAttribute(
38
+ 'href',
39
+ '/new/hosts?search=insights_inventory_sync_status+%3D+user_omitted&page=1'
40
+ );
41
+ expect(screen.getByText(omittedText, { exact: false })).toBeInTheDocument();
33
42
  });
34
43
 
35
44
  it('does not render user_omitted section when count is 0', () => {
36
- const wrapper = shallow(
37
- <Toast syncHosts={5} disconnectHosts={3} userOmittedHosts={0} />
38
- );
39
-
40
- // Should have only 2 HostsWithStatusLink components (sync and disconnect)
41
- const links = wrapper.find('HostsWithStatusLink');
42
- expect(links).toHaveLength(2);
45
+ renderToast({ userOmittedHosts: 0 });
43
46
 
44
- // Should not contain the user_omitted explanation text
45
- expect(wrapper.text()).not.toContain(
46
- 'host_registration_insights_inventory parameter value is false'
47
- );
47
+ expect(screen.getByRole('link', { name: '8' })).toBeInTheDocument();
48
+ expect(screen.getByRole('link', { name: '5' })).toBeInTheDocument();
49
+ expect(screen.getByRole('link', { name: '3' })).toBeInTheDocument();
50
+ expect(screen.queryByRole('link', { name: '2' })).not.toBeInTheDocument();
51
+ expect(
52
+ screen.queryByText(omittedText, { exact: false })
53
+ ).not.toBeInTheDocument();
48
54
  });
49
55
 
50
56
  it('renders without crashing when userOmittedHosts is not provided (default)', () => {
51
- const wrapper = shallow(<Toast syncHosts={5} disconnectHosts={3} />);
52
-
53
- // Should use default value of 0, so only 2 links
54
- const links = wrapper.find('HostsWithStatusLink');
55
- expect(links).toHaveLength(2);
57
+ renderToast();
56
58
 
57
- // Verify the count values
59
+ expect(screen.getByRole('link', { name: '8' })).toBeInTheDocument();
60
+ expect(screen.getByRole('link', { name: '5' })).toBeInTheDocument();
61
+ expect(screen.getByRole('link', { name: '3' })).toBeInTheDocument();
58
62
  expect(
59
- links
60
- .at(0)
61
- .children()
62
- .text()
63
- ).toBe('5');
64
- expect(
65
- links
66
- .at(1)
67
- .children()
68
- .text()
69
- ).toBe('3');
63
+ screen.queryByText(omittedText, { exact: false })
64
+ ).not.toBeInTheDocument();
70
65
  });
71
66
 
72
67
  it('renders correct status links for each category', () => {
73
- const wrapper = shallow(
74
- <Toast syncHosts={5} disconnectHosts={3} userOmittedHosts={2} />
75
- );
68
+ renderToast({ userOmittedHosts: 2 });
76
69
 
77
- const links = wrapper.find('HostsWithStatusLink');
78
- expect(links.at(0).prop('statusName')).toBe('sync');
79
- expect(links.at(1).prop('statusName')).toBe('disconnect');
80
- expect(links.at(2).prop('statusName')).toBe('user_omitted');
70
+ expect(screen.getByRole('link', { name: '5' })).toHaveAttribute(
71
+ 'href',
72
+ expect.stringContaining('sync')
73
+ );
74
+ expect(screen.getByRole('link', { name: '3' })).toHaveAttribute(
75
+ 'href',
76
+ expect.stringContaining('disconnect')
77
+ );
78
+ expect(screen.getByRole('link', { name: '2' })).toHaveAttribute(
79
+ 'href',
80
+ expect.stringContaining('user_omitted')
81
+ );
81
82
  });
82
83
  });
@@ -1,17 +1,78 @@
1
1
  import React from 'react';
2
- import { IntegrationTestHelper } from '@theforeman/test';
2
+ import { screen, waitFor } from '@testing-library/react';
3
+ import '@testing-library/jest-dom';
4
+ import { Provider } from 'react-redux';
5
+ import { applyMiddleware, combineReducers, createStore } from 'redux';
6
+ import thunk from 'redux-thunk';
7
+ import { rtlHelpers } from 'foremanReact/common/rtlTestHelpers';
8
+ import { API } from 'foremanReact/redux/API';
3
9
 
4
10
  import InsightsTab from '../index';
5
11
  import reducers from '../../ForemanRhCloudReducers';
6
- import { hostID } from './InsightsTab.fixtures';
12
+ import { INSIGHTS_HITS_SUCCESS } from '../InsightsTabConstants';
13
+ import { hostID, hits } from './InsightsTab.fixtures';
7
14
 
8
- describe('InsightsTab integration test', () => {
9
- it('should flow', async () => {
10
- const integrationTestHelper = new IntegrationTestHelper(reducers);
11
- const component = integrationTestHelper.mount(
15
+ const { renderWithI18n } = rtlHelpers;
16
+
17
+ jest.mock('foremanReact/redux/API');
18
+
19
+ const recommendationTitle =
20
+ 'New Ansible Engine packages are inaccessible when dedicated Ansible repo is not enabled';
21
+
22
+ const createTabStore = () =>
23
+ createStore(combineReducers({ ...reducers }), applyMiddleware(thunk));
24
+
25
+ const renderConnectedTab = (store = createTabStore()) =>
26
+ renderWithI18n(
27
+ <Provider store={store}>
12
28
  <InsightsTab hostID={hostID} />
29
+ </Provider>
30
+ );
31
+
32
+ describe('InsightsTab integration test', () => {
33
+ beforeEach(() => {
34
+ jest.clearAllMocks();
35
+ });
36
+
37
+ it('requests hits for the host and shows empty state when there are none', async () => {
38
+ let resolveHits;
39
+ API.get.mockReturnValue(
40
+ new Promise(resolve => {
41
+ resolveHits = resolve;
42
+ })
13
43
  );
14
- component.update();
15
- /** Create a Flow test */
44
+
45
+ const store = createTabStore();
46
+ store.dispatch({
47
+ type: INSIGHTS_HITS_SUCCESS,
48
+ payload: { hits },
49
+ });
50
+
51
+ renderConnectedTab(store);
52
+
53
+ expect(await screen.findByText(recommendationTitle)).toBeInTheDocument();
54
+
55
+ await waitFor(() => {
56
+ expect(API.get).toHaveBeenCalledWith(`/insights_cloud/hits/${hostID}`);
57
+ });
58
+
59
+ resolveHits({ data: { hits: [] } });
60
+
61
+ expect(
62
+ await screen.findByRole('heading', {
63
+ name: 'No recommendations were found for this host!',
64
+ })
65
+ ).toBeInTheDocument();
66
+ expect(screen.queryByText(recommendationTitle)).not.toBeInTheDocument();
67
+ });
68
+
69
+ it('shows recommendation titles from the hits API response', async () => {
70
+ API.get.mockResolvedValue({ data: { hits } });
71
+
72
+ renderConnectedTab();
73
+
74
+ expect(await screen.findByText(recommendationTitle)).toBeInTheDocument();
75
+
76
+ expect(API.get).toHaveBeenCalledWith(`/insights_cloud/hits/${hostID}`);
16
77
  });
17
78
  });
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: 14.5.0
4
+ version: 14.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Foreman Red Hat Cloud team