From 39f7d18b56a5aa22be0240a9909974df785c54af Mon Sep 17 00:00:00 2001 From: Griffin Sullivan <48397354+Griffin-Sullivan@users.noreply.github.com> Date: Wed, 12 Jun 2024 16:22:11 -0400 Subject: [PATCH 01/13] Fix remove satellite packages test (#15397) Fix remote satellite packages test --- tests/foreman/destructive/test_packages.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/foreman/destructive/test_packages.py b/tests/foreman/destructive/test_packages.py index c7098c98924..e3bb7560baf 100644 --- a/tests/foreman/destructive/test_packages.py +++ b/tests/foreman/destructive/test_packages.py @@ -84,5 +84,5 @@ def test_negative_remove_satellite_packages(sat_maintain): assert result.status != 0 assert ( 'Problem: The operation would result in removing the following protected packages: satellite' - in str(result.stderr[1]) + in str(result.stderr) ) From 8fab3b195f0c7cda85d0e38d49b6a9c51ce991fc Mon Sep 17 00:00:00 2001 From: yanpliu Date: Thu, 13 Jun 2024 06:09:51 -0400 Subject: [PATCH 02/13] Check Hypervisor host subscription status and hypervisor host and virtual guest mapping in UI (#15288) * Check Hypervisor host subscription status and hypervisor host and virtual guest mapping in UI * optimize the duplicated code to method --- robottelo/utils/virtwho.py | 48 +++++++++++++++++++ tests/foreman/virtwho/ui/test_esx_sca.py | 23 ++++----- tests/foreman/virtwho/ui/test_hyperv_sca.py | 23 ++++----- tests/foreman/virtwho/ui/test_kubevirt_sca.py | 23 ++++----- tests/foreman/virtwho/ui/test_libvirt_sca.py | 23 ++++----- tests/foreman/virtwho/ui/test_nutanix_sca.py | 23 ++++----- 6 files changed, 108 insertions(+), 55 deletions(-) diff --git a/robottelo/utils/virtwho.py b/robottelo/utils/virtwho.py index a6f638b2f21..279ad2acee2 100644 --- a/robottelo/utils/virtwho.py +++ b/robottelo/utils/virtwho.py @@ -600,3 +600,51 @@ def vw_run_option(option): runcmd('systemctl stop virt-who') runcmd('pkill -9 virt-who') runcmd(f'virt-who -{option}') + + +def hypervisor_guest_mapping_check_legacy_ui( + org_session, form_data_ui, default_location, hypervisor_name, guest_name +): + # Check virt-who config status + assert org_session.virtwho_configure.search(form_data_ui['name'])[0]['Status'] == 'ok' + # Check Hypervisor host subscription status and hypervisor host and virtual guest mapping in Legacy UI + org_session.location.select(default_location.name) + hypervisor_display_name = org_session.contenthost.search(hypervisor_name)[0]['Name'] + hypervisorhost = org_session.contenthost.read_legacy_ui(hypervisor_display_name) + assert hypervisorhost['details']['subscription_status'] == 'Simple Content Access' + assert hypervisorhost['details']['virtual_guest'] == '1 Content Host' + # Check virtual guest subscription status and hypervisor host and virtual guest mapping in Legacy UI + virtualguest = org_session.contenthost.read_legacy_ui(guest_name) + assert virtualguest['details']['subscription_status'] == 'Simple Content Access' + assert virtualguest['details']['virtual_host'] == hypervisor_display_name + + +def hypervisor_guest_mapping_newcontent_ui(org_session, hypervisor_name, guest_name): + hypervisor_display_name = org_session.contenthost.search(hypervisor_name)[0]['Name'] + hypervisorhost_new_overview = org_session.host_new.get_details( + hypervisor_display_name, 'overview' + ) + assert hypervisorhost_new_overview['overview']['host_status']['status_success'] == '2' + # hypervisor host Check details + hypervisorhost_new_detais = org_session.host_new.get_details(hypervisor_display_name, 'details') + assert ( + hypervisorhost_new_detais['details']['system_properties']['sys_properties']['virtual_host'] + == hypervisor_display_name + ) + assert ( + hypervisorhost_new_detais['details']['system_properties']['sys_properties']['name'] + == guest_name + ) + # Check guest overview + guest_new_overview = org_session.host_new.get_details(guest_name, 'overview') + assert guest_new_overview['overview']['host_status']['status_success'] == '2' + # Check guest details + virtualguest_new_detais = org_session.host_new.get_details(guest_name, 'details') + assert ( + virtualguest_new_detais['details']['system_properties']['sys_properties']['virtual_host'] + == hypervisor_display_name + ) + assert ( + virtualguest_new_detais['details']['system_properties']['sys_properties']['name'] + == guest_name + ) diff --git a/tests/foreman/virtwho/ui/test_esx_sca.py b/tests/foreman/virtwho/ui/test_esx_sca.py index 9bbeb9597cd..26731db8d44 100644 --- a/tests/foreman/virtwho/ui/test_esx_sca.py +++ b/tests/foreman/virtwho/ui/test_esx_sca.py @@ -29,6 +29,8 @@ get_configure_id, get_configure_option, get_virtwho_status, + hypervisor_guest_mapping_check_legacy_ui, + hypervisor_guest_mapping_newcontent_ui, restart_virtwho_service, update_configure_option, ) @@ -51,23 +53,22 @@ def test_positive_deploy_configure_by_id_script( 2. No error msg in /var/log/rhsm/rhsm.log 3. Report is sent to satellite 4. Subscription Status set to 'Simple Content Access', and generate mapping in Legacy UI - 5. Config can be deleted + 5. Check Hypervisor host subscription status and hypervisor host and virtual guest mapping in UI + 6. Config can be deleted :CaseImportance: High """ hypervisor_name, guest_name = deploy_type_ui - # Check virt-wh oconfig status + # Check virt-who config status assert org_session.virtwho_configure.search(form_data_ui['name'])[0]['Status'] == 'ok' + # Check Hypervisor host subscription status and hypervisor host and virtual guest mapping in Legacy UI - org_session.location.select(default_location.name) - hypervisor_display_name = org_session.contenthost.search(hypervisor_name)[0]['Name'] - hypervisorhost = org_session.contenthost.read_legacy_ui(hypervisor_display_name) - assert hypervisorhost['details']['subscription_status'] == 'Simple Content Access' - assert hypervisorhost['details']['virtual_guest'] == '1 Content Host' - # Check virtual guest subscription status and hypervisor host and virtual guest mapping in Legacy UI - virtualguest = org_session.contenthost.read_legacy_ui(guest_name) - assert virtualguest['details']['subscription_status'] == 'Simple Content Access' - assert virtualguest['details']['virtual_host'] == hypervisor_display_name + hypervisor_guest_mapping_check_legacy_ui( + org_session, form_data_ui, default_location, hypervisor_name, guest_name + ) + + # Check Hypervisor host subscription status and hypervisor host and virtual guest mapping in UI + hypervisor_guest_mapping_newcontent_ui(org_session, hypervisor_name, guest_name) @pytest.mark.tier2 def test_positive_debug_option( diff --git a/tests/foreman/virtwho/ui/test_hyperv_sca.py b/tests/foreman/virtwho/ui/test_hyperv_sca.py index d0b2062677a..657a84c9d1b 100644 --- a/tests/foreman/virtwho/ui/test_hyperv_sca.py +++ b/tests/foreman/virtwho/ui/test_hyperv_sca.py @@ -18,6 +18,8 @@ get_configure_file, get_configure_id, get_configure_option, + hypervisor_guest_mapping_check_legacy_ui, + hypervisor_guest_mapping_newcontent_ui, ) @@ -36,23 +38,22 @@ def test_positive_deploy_configure_by_id_script( 2. No error msg in /var/log/rhsm/rhsm.log 3. Report is sent to satellite 4. Subscription Status set to 'Simple Content Access', and generate mapping in Legacy UI - 5. Config can be deleted + 5. Check Hypervisor host subscription status and hypervisor host and virtual guest mapping in UI + 6. Config can be deleted :CaseImportance: High """ hypervisor_name, guest_name = deploy_type_ui - # Check virt-wh oconfig status + # Check virt-who config status assert org_session.virtwho_configure.search(form_data_ui['name'])[0]['Status'] == 'ok' + # Check Hypervisor host subscription status and hypervisor host and virtual guest mapping in Legacy UI - org_session.location.select(default_location.name) - hypervisor_display_name = org_session.contenthost.search(hypervisor_name)[0]['Name'] - hypervisorhost = org_session.contenthost.read_legacy_ui(hypervisor_display_name) - assert hypervisorhost['details']['subscription_status'] == 'Simple Content Access' - assert hypervisorhost['details']['virtual_guest'] == '1 Content Host' - # Check virtual guest subscription status and hypervisor host and virtual guest mapping in Legacy UI - virtualguest = org_session.contenthost.read_legacy_ui(guest_name) - assert virtualguest['details']['subscription_status'] == 'Simple Content Access' - assert virtualguest['details']['virtual_host'] == hypervisor_display_name + hypervisor_guest_mapping_check_legacy_ui( + org_session, form_data_ui, default_location, hypervisor_name, guest_name + ) + + # Check Hypervisor host subscription status and hypervisor host and virtual guest mapping in UI + hypervisor_guest_mapping_newcontent_ui(org_session, hypervisor_name, guest_name) @pytest.mark.tier2 def test_positive_hypervisor_id_option( diff --git a/tests/foreman/virtwho/ui/test_kubevirt_sca.py b/tests/foreman/virtwho/ui/test_kubevirt_sca.py index 928243755d5..1696337f804 100644 --- a/tests/foreman/virtwho/ui/test_kubevirt_sca.py +++ b/tests/foreman/virtwho/ui/test_kubevirt_sca.py @@ -18,6 +18,8 @@ get_configure_file, get_configure_id, get_configure_option, + hypervisor_guest_mapping_check_legacy_ui, + hypervisor_guest_mapping_newcontent_ui, ) @@ -36,23 +38,22 @@ def test_positive_deploy_configure_by_id_script( 2. No error msg in /var/log/rhsm/rhsm.log 3. Report is sent to satellite 4. Subscription Status set to 'Simple Content Access', and generate mapping in Legacy UI - 5. Config can be deleted + 5. Check Hypervisor host subscription status and hypervisor host and virtual guest mapping in UI + 6. Config can be deleted :CaseImportance: High """ hypervisor_name, guest_name = deploy_type_ui - # Check virt-wh oconfig status + # Check virt-who config status assert org_session.virtwho_configure.search(form_data_ui['name'])[0]['Status'] == 'ok' + # Check Hypervisor host subscription status and hypervisor host and virtual guest mapping in Legacy UI - org_session.location.select(default_location.name) - hypervisor_display_name = org_session.contenthost.search(hypervisor_name)[0]['Name'] - hypervisorhost = org_session.contenthost.read_legacy_ui(hypervisor_display_name) - assert hypervisorhost['details']['subscription_status'] == 'Simple Content Access' - assert hypervisorhost['details']['virtual_guest'] == '1 Content Host' - # Check virtual guest subscription status and hypervisor host and virtual guest mapping in Legacy UI - virtualguest = org_session.contenthost.read_legacy_ui(guest_name) - assert virtualguest['details']['subscription_status'] == 'Simple Content Access' - assert virtualguest['details']['virtual_host'] == hypervisor_display_name + hypervisor_guest_mapping_check_legacy_ui( + org_session, form_data_ui, default_location, hypervisor_name, guest_name + ) + + # Check Hypervisor host subscription status and hypervisor host and virtual guest mapping in UI + hypervisor_guest_mapping_newcontent_ui(org_session, hypervisor_name, guest_name) @pytest.mark.tier2 def test_positive_hypervisor_id_option( diff --git a/tests/foreman/virtwho/ui/test_libvirt_sca.py b/tests/foreman/virtwho/ui/test_libvirt_sca.py index e0d4d67db7c..61d81245dcd 100644 --- a/tests/foreman/virtwho/ui/test_libvirt_sca.py +++ b/tests/foreman/virtwho/ui/test_libvirt_sca.py @@ -18,6 +18,8 @@ get_configure_file, get_configure_id, get_configure_option, + hypervisor_guest_mapping_check_legacy_ui, + hypervisor_guest_mapping_newcontent_ui, ) @@ -36,23 +38,22 @@ def test_positive_deploy_configure_by_id_script( 2. No error msg in /var/log/rhsm/rhsm.log 3. Report is sent to satellite 4. Subscription Status set to 'Simple Content Access', and generate mapping in Legacy UI - 5. Config can be deleted + 5. Check Hypervisor host subscription status and hypervisor host and virtual guest mapping in UI + 6. Config can be deleted :CaseImportance: High """ hypervisor_name, guest_name = deploy_type_ui - # Check virt-wh oconfig status + # Check virt-who config status assert org_session.virtwho_configure.search(form_data_ui['name'])[0]['Status'] == 'ok' + # Check Hypervisor host subscription status and hypervisor host and virtual guest mapping in Legacy UI - org_session.location.select(default_location.name) - hypervisor_display_name = org_session.contenthost.search(hypervisor_name)[0]['Name'] - hypervisorhost = org_session.contenthost.read_legacy_ui(hypervisor_display_name) - assert hypervisorhost['details']['subscription_status'] == 'Simple Content Access' - assert hypervisorhost['details']['virtual_guest'] == '1 Content Host' - # Check virtual guest subscription status and hypervisor host and virtual guest mapping in Legacy UI - virtualguest = org_session.contenthost.read_legacy_ui(guest_name) - assert virtualguest['details']['subscription_status'] == 'Simple Content Access' - assert virtualguest['details']['virtual_host'] == hypervisor_display_name + hypervisor_guest_mapping_check_legacy_ui( + org_session, form_data_ui, default_location, hypervisor_name, guest_name + ) + + # Check Hypervisor host subscription status and hypervisor host and virtual guest mapping in UI + hypervisor_guest_mapping_newcontent_ui(org_session, hypervisor_name, guest_name) @pytest.mark.tier2 def test_positive_hypervisor_id_option( diff --git a/tests/foreman/virtwho/ui/test_nutanix_sca.py b/tests/foreman/virtwho/ui/test_nutanix_sca.py index e98c9ba809b..3850b5feaeb 100644 --- a/tests/foreman/virtwho/ui/test_nutanix_sca.py +++ b/tests/foreman/virtwho/ui/test_nutanix_sca.py @@ -22,6 +22,8 @@ get_configure_id, get_configure_option, get_hypervisor_ahv_mapping, + hypervisor_guest_mapping_check_legacy_ui, + hypervisor_guest_mapping_newcontent_ui, ) @@ -40,23 +42,22 @@ def test_positive_deploy_configure_by_id_script( 2. No error msg in /var/log/rhsm/rhsm.log 3. Report is sent to satellite 4. Subscription Status set to 'Simple Content Access', and generate mapping in Legacy UI - 5. Config can be deleted + 5. Check Hypervisor host subscription status and hypervisor host and virtual guest mapping in UI + 6. Config can be deleted :CaseImportance: High """ hypervisor_name, guest_name = deploy_type_ui - # Check virt-wh oconfig status + # Check virt-who config status assert org_session.virtwho_configure.search(form_data_ui['name'])[0]['Status'] == 'ok' + # Check Hypervisor host subscription status and hypervisor host and virtual guest mapping in Legacy UI - org_session.location.select(default_location.name) - hypervisor_display_name = org_session.contenthost.search(hypervisor_name)[0]['Name'] - hypervisorhost = org_session.contenthost.read_legacy_ui(hypervisor_display_name) - assert hypervisorhost['details']['subscription_status'] == 'Simple Content Access' - assert hypervisorhost['details']['virtual_guest'] == '1 Content Host' - # Check virtual guest subscription status and hypervisor host and virtual guest mapping in Legacy UI - virtualguest = org_session.contenthost.read_legacy_ui(guest_name) - assert virtualguest['details']['subscription_status'] == 'Simple Content Access' - assert virtualguest['details']['virtual_host'] == hypervisor_display_name + hypervisor_guest_mapping_check_legacy_ui( + org_session, form_data_ui, default_location, hypervisor_name, guest_name + ) + + # Check Hypervisor host subscription status and hypervisor host and virtual guest mapping in UI + hypervisor_guest_mapping_newcontent_ui(org_session, hypervisor_name, guest_name) @pytest.mark.tier2 def test_positive_hypervisor_id_option( From 423864f72c811278e59bc2160247018ec2e4b4be Mon Sep 17 00:00:00 2001 From: amolpati30 <151733635+amolpati30@users.noreply.github.com> Date: Thu, 13 Jun 2024 21:11:49 +0530 Subject: [PATCH 03/13] verify host owner name after host registration (#15178) * verify host name after host registration * assertion added for host --- tests/foreman/cli/test_registration.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/tests/foreman/cli/test_registration.py b/tests/foreman/cli/test_registration.py index 22d24ae517e..8e7927a3306 100644 --- a/tests/foreman/cli/test_registration.py +++ b/tests/foreman/cli/test_registration.py @@ -42,10 +42,11 @@ def test_host_registration_end_to_end( :steps: 1. Register host with global registration template to Satellite and Capsule + 2. Check the host is registered and verify host owner name - :expectedresults: Host registered successfully + :expectedresults: Host registered successfully with valid owner name - :BZ: 2156926 + :BZ: 2156926, 2252768 :customerscenario: true """ @@ -57,6 +58,14 @@ def test_host_registration_end_to_end( rc = 1 if rhel_contenthost.os_version.major == 6 else 0 assert result.status == rc, f'Failed to register host: {result.stderr}' + owner_name = module_target_sat.cli.Host.info( + options={'name': rhel_contenthost.hostname, 'fields': 'Additional info/owner'} + ) + # Verify host owner name set correctly + assert 'Admin User' in owner_name['additional-info']['owner']['name'], ( + f'Host owner name is incorrect: ' f'{owner_name["additional-info"]["owner"]["name"]}' + ) + # Verify server.hostname and server.port from subscription-manager config assert module_target_sat.hostname == rhel_contenthost.subscription_config['server']['hostname'] assert rhel_contenthost.subscription_config['server']['port'] == CLIENT_PORT @@ -79,6 +88,14 @@ def test_host_registration_end_to_end( rc = 1 if rhel_contenthost.os_version.major == 6 else 0 assert result.status == rc, f'Failed to register host: {result.stderr}' + owner_name = module_target_sat.cli.Host.info( + options={'name': rhel_contenthost.hostname, 'fields': 'Additional info/owner'} + ) + # Verify capsule host owner name set correctly + assert 'Admin User' in owner_name['additional-info']['owner']['name'], ( + f'Host owner name is incorrect: ' f'{owner_name["additional-info"]["owner"]["name"]}' + ) + # Verify server.hostname and server.port from subscription-manager config assert ( module_capsule_configured.hostname From 264744b07585c4162ebf5c3b6d705e76de5de041 Mon Sep 17 00:00:00 2001 From: Jitendra Yejare Date: Fri, 14 Jun 2024 02:35:32 +0530 Subject: [PATCH 04/13] Enabling Robottelo for IPv6 Testing (#14160) * Enabling Robottelo for IPv6 Testing * Enabling broker context managers for ipv6 network * Translating URLs for Ipv6 * Sanity Testing fixes for Ipv6 * Review Fixes * Removed the explicit http proxy teardowns * Git checks fixes --- conf/capsule.yaml.template | 1 + conf/dynaconf_hooks.py | 3 +- conf/server.yaml.template | 5 ++ pytest_fixtures/component/maintain.py | 4 +- pytest_fixtures/core/broker.py | 3 + pytest_fixtures/core/sat_cap_factory.py | 18 ++-- pytest_fixtures/core/xdist.py | 5 +- robottelo/config/validators.py | 2 + robottelo/host_helpers/contenthost_mixins.py | 11 ++- robottelo/hosts.py | 94 +++++++++++++++++++- robottelo/utils/ohsnap.py | 17 ++-- robottelo/utils/url.py | 41 +++++++++ tests/foreman/installer/test_installer.py | 12 +-- 13 files changed, 191 insertions(+), 25 deletions(-) diff --git a/conf/capsule.yaml.template b/conf/capsule.yaml.template index b618bf57ea9..4f50d26bfaf 100644 --- a/conf/capsule.yaml.template +++ b/conf/capsule.yaml.template @@ -17,3 +17,4 @@ CAPSULE: OS: deploy-rhel # workflow to deploy OS that is ready to run the product # Dictionary of arguments which should be passed along to the deploy workflow DEPLOY_ARGUMENTS: + # deploy_network_type: '@jinja {{"ipv6" if this.server.is_ipv6 else "ipv4"}}' diff --git a/conf/dynaconf_hooks.py b/conf/dynaconf_hooks.py index 85baf7d52cb..b3d7a8e219c 100644 --- a/conf/dynaconf_hooks.py +++ b/conf/dynaconf_hooks.py @@ -7,7 +7,7 @@ from robottelo.logging import logger from robottelo.utils.ohsnap import dogfood_repository -from robottelo.utils.url import is_url +from robottelo.utils.url import ipv6_hostname_translation, is_url def post(settings): @@ -26,6 +26,7 @@ def post(settings): ) data = get_repos_config(settings) write_cache(settings_cache_path, data) + ipv6_hostname_translation(settings, data) config_migrations(settings, data) data['dynaconf_merge'] = True return data diff --git a/conf/server.yaml.template b/conf/server.yaml.template index c5b844e6509..e196dd9413d 100644 --- a/conf/server.yaml.template +++ b/conf/server.yaml.template @@ -13,6 +13,10 @@ SERVER: SOURCE: "internal" # The RHEL Base OS Version(x.y) where the Satellite is installed RHEL_VERSION: '7' + # If the the satellite server is IPv6 server + IS_IPV6: False + # HTTP Proxy url for IPv6 satellite to connect for outer world access + HTTP_PROXY_IPv6_URL: # run-on-one - All xdist runners default to the first satellite # balance - xdist runners will be split between available satellites # on-demand - any xdist runner without a satellite will have a new one provisioned. @@ -32,6 +36,7 @@ SERVER: OS: deploy-rhel # workflow to deploy OS that is ready to run the product # Dictionary of arguments which should be passed along to the deploy workflow # DEPLOY_ARGUMENTS: + # deploy_network_type: '@jinja {{"ipv6" if this.server.is_ipv6 else "ipv4"}}' # HTTP scheme when building the server URL # Suggested values for "scheme" are "http" and "https". SCHEME: https diff --git a/pytest_fixtures/component/maintain.py b/pytest_fixtures/component/maintain.py index 68aa82f5c86..1e0e264e0ca 100644 --- a/pytest_fixtures/component/maintain.py +++ b/pytest_fixtures/component/maintain.py @@ -24,7 +24,9 @@ def module_stash(request): @pytest.fixture(scope='module') def sat_maintain(request, module_target_sat, module_capsule_configured): if settings.remotedb.server: - yield Satellite(settings.remotedb.server) + sat = Satellite(settings.remotedb.server) + sat.enable_ipv6_http_proxy() + yield sat else: module_target_sat.register_to_cdn(pool_ids=settings.subscription.fm_rhn_poolid.split()) hosts = {'satellite': module_target_sat, 'capsule': module_capsule_configured} diff --git a/pytest_fixtures/core/broker.py b/pytest_fixtures/core/broker.py index f3356dbbef5..9e63ce92a14 100644 --- a/pytest_fixtures/core/broker.py +++ b/pytest_fixtures/core/broker.py @@ -24,6 +24,7 @@ def _target_sat_imp(request, _default_sat, satellite_factory): """This is the actual working part of the following target_sat fixtures""" if request.node.get_closest_marker(name='destructive'): new_sat = satellite_factory() + new_sat.enable_ipv6_http_proxy() yield new_sat new_sat.teardown() Broker(hosts=[new_sat]).checkin() @@ -32,6 +33,8 @@ def _target_sat_imp(request, _default_sat, satellite_factory): settings.set('server.hostname', installer_sat.hostname) yield installer_sat else: + if _default_sat: + _default_sat.enable_ipv6_http_proxy() yield _default_sat diff --git a/pytest_fixtures/core/sat_cap_factory.py b/pytest_fixtures/core/sat_cap_factory.py index 9634c6c4fcb..4c38c77546f 100644 --- a/pytest_fixtures/core/sat_cap_factory.py +++ b/pytest_fixtures/core/sat_cap_factory.py @@ -31,6 +31,7 @@ def resolve_deploy_args(args_dict): def _target_satellite_host(request, satellite_factory): if 'sanity' not in request.config.option.markexpr: new_sat = satellite_factory() + new_sat.enable_ipv6_http_proxy() yield new_sat new_sat.teardown() Broker(hosts=[new_sat]).checkin() @@ -48,6 +49,7 @@ def cached_capsule_cdn_register(hostname=None): def _target_capsule_host(request, capsule_factory): if 'sanity' not in request.config.option.markexpr and not request.config.option.n_minus: new_cap = capsule_factory() + new_cap.enable_ipv6_http_proxy() yield new_cap new_cap.teardown() Broker(hosts=[new_cap]).checkin() @@ -94,6 +96,7 @@ def factory(retry_limit=3, delay=300, workflow=None, **broker_args): def large_capsule_host(capsule_factory): """A fixture that provides a Capsule based on config settings""" new_cap = capsule_factory(deploy_flavor=settings.flavors.custom_db) + new_cap.enable_ipv6_http_proxy() yield new_cap new_cap.teardown() Broker(hosts=[new_cap]).checkin() @@ -244,6 +247,7 @@ def module_lb_capsule(retry_limit=3, delay=300, **broker_args): ) cap_hosts = wait_for(hosts.checkout, timeout=timeout, delay=delay) + [cap.enable_ipv6_http_proxy() for cap in cap_hosts.out] yield cap_hosts.out [cap.teardown() for cap in cap_hosts.out] @@ -278,6 +282,7 @@ def parametrized_enrolled_sat( ): """Yields a Satellite enrolled into [IDM, AD] as parameter.""" new_sat = satellite_factory() + new_sat.enable_ipv6_http_proxy() ipa_host = IPAHost(new_sat) new_sat.register_to_cdn() if 'IDM' in request.param: @@ -297,6 +302,7 @@ def get_deploy_args(request): rhel_version = get_sat_rhel_version() deploy_args = { 'deploy_rhel_version': rhel_version.base_version, + 'deploy_network_type': 'ipv6' if settings.server.is_ipv6 else 'ipv4', 'deploy_flavor': settings.flavors.default, 'promtail_config_template_file': 'config_sat.j2', 'workflow': settings.server.deploy_workflows.os, @@ -328,11 +334,13 @@ def cap_ready_rhel(): rhel_version = Version(settings.capsule.version.rhel_version) deploy_args = { 'deploy_rhel_version': rhel_version.base_version, + 'deploy_network_type': 'ipv6' if settings.server.is_ipv6 else 'ipv4', 'deploy_flavor': settings.flavors.default, 'promtail_config_template_file': 'config_sat.j2', 'workflow': settings.capsule.deploy_workflows.os, } with Broker(**deploy_args, host_class=Capsule) as host: + host.enable_ipv6_http_proxy() yield host @@ -352,7 +360,7 @@ def installer_satellite(request): sat = lru_sat_ready_rhel(getattr(request, 'param', None)) sat.setup_firewall() # # Register for RHEL8 repos, get Ohsnap repofile, and enable and download satellite - sat.register_to_cdn() + sat.register_to_cdn(enable_proxy=True) sat.download_repofile( product='satellite', release=settings.server.version.release, @@ -371,12 +379,12 @@ def installer_satellite(request): ).get_command(), timeout='30m', ) + sat.enable_ipv6_http_proxy() if 'sanity' in request.config.option.markexpr: configure_nailgun() configure_airgun() yield sat if 'sanity' not in request.config.option.markexpr: - sanity_sat = Satellite(sat.hostname) - sanity_sat.unregister() - broker_sat = Satellite.get_host_by_hostname(sanity_sat.hostname) - Broker(hosts=[broker_sat]).checkin() + sat = Satellite.get_host_by_hostname(sat.hostname) + sat.unregister() + Broker(hosts=[sat]).checkin() diff --git a/pytest_fixtures/core/xdist.py b/pytest_fixtures/core/xdist.py index be497dd988d..216589e85d7 100644 --- a/pytest_fixtures/core/xdist.py +++ b/pytest_fixtures/core/xdist.py @@ -19,8 +19,9 @@ def align_to_satellite(request, worker_id, satellite_factory): if settings.server.hostname: sanity_sat = Satellite(settings.server.hostname) sanity_sat.unregister() - broker_sat = Satellite.get_host_by_hostname(sanity_sat.hostname) - Broker(hosts=[broker_sat]).checkin() + if settings.server.auto_checkin: + broker_sat = Satellite.get_host_by_hostname(sanity_sat.hostname) + Broker(hosts=[broker_sat]).checkin() else: # clear any hostname that may have been previously set settings.set("server.hostname", None) diff --git a/robottelo/config/validators.py b/robottelo/config/validators.py index 9380cbb5267..1c37d63404f 100644 --- a/robottelo/config/validators.py +++ b/robottelo/config/validators.py @@ -35,6 +35,8 @@ Validator('server.ssh_username', default='root'), Validator('server.ssh_password', default=None), Validator('server.verify_ca', default=False), + Validator('server.is_ipv6', is_type_of=bool, default=False), + Validator('server.http_proxy_ipv6_url', is_type_of=str, default=None), ], content_host=[ Validator('content_host.default_rhel_version', must_exist=True), diff --git a/robottelo/host_helpers/contenthost_mixins.py b/robottelo/host_helpers/contenthost_mixins.py index c76d23405fe..2fcfd909d3e 100644 --- a/robottelo/host_helpers/contenthost_mixins.py +++ b/robottelo/host_helpers/contenthost_mixins.py @@ -90,11 +90,16 @@ def _dogfood_helper(self, product, release, repo=None): ) return product, release, v_major, repo - def download_repofile(self, product=None, release=None, snap=''): + def download_repofile(self, product=None, release=None, snap='', proxy=None): """Downloads the tools/client, capsule, or satellite repos on the machine""" product, release, v_major, _ = self._dogfood_helper(product, release) - url = dogfood_repofile_url(settings.ohsnap, product, release, v_major, snap) - self.execute(f'curl -o /etc/yum.repos.d/dogfood.repo -L {url}') + if not proxy and settings.server.is_ipv6: + proxy = settings.server.http_proxy_ipv6_url + url = dogfood_repofile_url(settings.ohsnap, product, release, v_major, snap, proxy=proxy) + command = f'curl -o /etc/yum.repos.d/dogfood.repo -L {url}' + if settings.server.is_ipv6: + command += f' -x {settings.server.http_proxy_ipv6_url}' + self.execute(command) def dogfood_repository(self, repo=None, product=None, release=None, snap=''): """Returns a repository definition based on the arguments provided""" diff --git a/robottelo/hosts.py b/robottelo/hosts.py index fcb01564cf3..68c789d050c 100644 --- a/robottelo/hosts.py +++ b/robottelo/hosts.py @@ -73,6 +73,7 @@ def lru_sat_ready_rhel(rhel_ver): rhel_version = rhel_ver or settings.server.version.rhel_version deploy_args = { 'deploy_rhel_version': rhel_version, + 'deploy_network_type': 'ipv6' if settings.server.is_ipv6 else 'ipv4', 'deploy_flavor': settings.flavors.default, 'promtail_config_template_file': 'config_sat.j2', 'workflow': settings.server.deploy_workflows.os, @@ -823,6 +824,7 @@ def register_contenthost( auto_attach=False, serverurl=None, baseurl=None, + enable_proxy=False, ): """Registers content host on foreman server either by specifying organization name and activation key name or by specifying organization @@ -879,6 +881,7 @@ def register_contenthost( cmd += f' --serverurl {serverurl}' if baseurl: cmd += f' --baseurl {baseurl}' + return self.execute(cmd) def unregister(self): @@ -925,6 +928,13 @@ def put_ssh_key(self, source_key_path, destination_key_name): if result.status != 0: raise CLIFactoryError(f'Failed to chmod ssh key file:\n{result.stderr}') + def enable_rhsm_proxy(self, hostname, port=None): + """Configures proxy for subscription manager""" + cmd = f"subscription-manager config --server.proxy_hostname={hostname}" + if port: + cmd += f' --server.proxy_port={port}' + self.execute(cmd) + def add_authorized_key(self, pub_key): """Inject a public key into the authorized keys file @@ -1469,7 +1479,7 @@ def install_tracer(self): raise ContentHostError('There was an error installing katello-host-tools-tracer') self.execute('katello-tracer-upload') - def register_to_cdn(self, pool_ids=None): + def register_to_cdn(self, pool_ids=None, enable_proxy=False): """Subscribe satellite to CDN""" if pool_ids is None: pool_ids = [settings.subscription.rhn_poolid] @@ -1479,6 +1489,7 @@ def register_to_cdn(self, pool_ids=None): lce=None, username=settings.subscription.rhn_username, password=settings.subscription.rhn_password, + enable_proxy=enable_proxy, ) if cmd_result.status != 0: raise ContentHostError( @@ -1633,6 +1644,17 @@ def enable_capsule_downstream_repos(self): snap=settings.capsule.version.snap, ) + def enable_ipv6_http_proxy(self): + """Execute procedures for enabling IPv6 HTTP Proxy on Capsule using SM""" + if all([settings.server.is_ipv6, settings.server.http_proxy_ipv6_url]): + url = urlparse(settings.server.http_proxy_ipv6_url) + self.enable_rhsm_proxy(url.hostname, url.port) + + def disable_ipv6_http_proxy(self): + """Executes procedures for disabling IPv6 HTTP Proxy on Capsule""" + if settings.server.is_ipv6: + self.execute('subscription-manager remove server.proxy_hostname server.proxy_port') + def capsule_setup(self, sat_host=None, capsule_cert_opts=None, **installer_kwargs): """Prepare the host and run the capsule installer""" self._satellite = sat_host or Satellite() @@ -1756,6 +1778,10 @@ def enable_satellite_or_capsule_module_for_rhel8(self): Note: Make sure required repos are enabled before using this. """ if self.os_version.major == 8: + if settings.server.is_ipv6: + self.execute( + f"echo -e 'proxy={settings.server.http_proxy_ipv6_url}' >> /etc/dnf/dnf.conf" + ) assert ( self.execute( f'dnf -y module enable {self.product_rpm_name}:el{self.os_version.major}' @@ -1798,6 +1824,55 @@ def _swap_nailgun(self, new_version): to_clear = [k for k in sys.modules if 'nailgun' in k] [sys.modules.pop(k) for k in to_clear] + def enable_ipv6_http_proxy(self): + """Execute procedures for enabling IPv6 HTTP Proxy""" + if not all([settings.server.is_ipv6, settings.server.http_proxy_ipv6_url]): + logger.warning( + 'The IPv6 HTTP Proxy setting is not enabled. Skipping the IPv6 HTTP Proxy setup.' + ) + return None + proxy_name = 'Robottelo IPv6 Automation Proxy' + if not self.cli.HttpProxy.exists(search=('name', proxy_name)): + http_proxy = self.api.HTTPProxy( + name=proxy_name, url=settings.server.http_proxy_ipv6_url + ).create() + else: + logger.info( + 'The IPv6 HTTP Proxy is already enabled. Skipping the IPv6 HTTP Proxy setup.' + ) + http_proxy = self.api.HTTPProxy().search(query={'search': f'name={proxy_name}'})[0] + # Setting HTTP Proxy as default in the settings + self.cli.Settings.set( + { + 'name': 'content_default_http_proxy', + 'value': proxy_name, + } + ) + self.cli.Settings.set( + { + 'name': 'http_proxy', + 'value': settings.server.http_proxy_ipv6_url, + } + ) + return http_proxy + + def disable_ipv6_http_proxy(self, http_proxy): + """Execute procedures for disabling IPv6 HTTP Proxy""" + if http_proxy: + http_proxy.delete() + self.cli.Settings.set( + { + 'name': 'content_default_http_proxy', + 'value': '', + } + ) + self.cli.Settings.set( + { + 'name': 'http_proxy', + 'value': '', + } + ) + @property def api(self): """Import all nailgun entities and wrap them under self.api""" @@ -2326,6 +2401,23 @@ def sync_inventory_status(self, org): ) return inventory_sync + def register_contenthost( + self, + org='Default_Organization', + lce='Library', + username=settings.server.admin_username, + password=settings.server.admin_password, + enable_proxy=False, + ): + """Satellite Registration to CDN""" + # Enabling proxy for IPv6 + if enable_proxy and all([settings.server.is_ipv6, settings.server.http_proxy_ipv6_url]): + url = urlparse(settings.server.http_proxy_ipv6_url) + self.enable_rhsm_proxy(url.hostname, url.port) + return super().register_contenthost( + org=org, lce=lce, username=username, password=password, enable_proxy=enable_proxy + ) + class SSOHost(Host): """Class for RHSSO functions and setup""" diff --git a/robottelo/utils/ohsnap.py b/robottelo/utils/ohsnap.py index 83669cdb61e..7e870888656 100644 --- a/robottelo/utils/ohsnap.py +++ b/robottelo/utils/ohsnap.py @@ -21,7 +21,7 @@ def ohsnap_response_hook(r, *args, **kwargs): r.raise_for_status() -def ohsnap_repo_url(ohsnap, request_type, product, release, os_release, snap=''): +def ohsnap_repo_url(ohsnap, request_type, product, release, os_release, snap='', proxy=None): """Returns a URL pointing to Ohsnap "repo_file" or "repositories" API endpoint""" if request_type not in ['repo_file', 'repositories']: raise InvalidArgumentError('Type must be one of "repo_file" or "repositories"') @@ -42,11 +42,14 @@ def ohsnap_repo_url(ohsnap, request_type, product, release, os_release, snap='') f'.z version component not provided in the release ({release}),' f' fetching the recent z-stream from ohsnap' ) + request_query = { + 'url': f'{ohsnap.host}/api/streams', + 'hooks': {'response': ohsnap_response_hook}, + } + if proxy: + request_query['proxies'] = {'http': proxy} res, _ = wait_for( - lambda: requests.get( - f'{ohsnap.host}/api/streams', - hooks={'response': ohsnap_response_hook}, - ), + lambda: requests.get(**request_query), handle_exception=True, raise_original=True, timeout=ohsnap.request_retry.timeout, @@ -69,8 +72,8 @@ def ohsnap_repo_url(ohsnap, request_type, product, release, os_release, snap='') ) -def dogfood_repofile_url(ohsnap, product, release, os_release, snap=''): - return ohsnap_repo_url(ohsnap, 'repo_file', product, release, os_release, snap) +def dogfood_repofile_url(ohsnap, product, release, os_release, snap='', proxy=None): + return ohsnap_repo_url(ohsnap, 'repo_file', product, release, os_release, snap, proxy) def dogfood_repository( diff --git a/robottelo/utils/url.py b/robottelo/utils/url.py index cadcfb56d14..a84a85d4c1c 100644 --- a/robottelo/utils/url.py +++ b/robottelo/utils/url.py @@ -1,5 +1,7 @@ from urllib.parse import urlparse +from robottelo.logging import logger + def is_url(url): try: @@ -7,3 +9,42 @@ def is_url(url): return all([result.scheme, result.netloc]) except (ValueError, AttributeError): return False + + +def is_ipv4_url(text): + """Verify if the URL is IPv4 url""" + return isinstance(text, str) and 'ipv4' in text and 'redhat.com' in text + + +def ipv6_translator(settings_list, setting_major, data): + """Translates the hostname containing IPv4 to IPv6 and updates the settings object""" + dotted_settings = '.'.join(setting_major) + for _key, _val in settings_list.items(): + if is_ipv4_url(_val): + data[f'{dotted_settings}.{_key}'] = str(_val).replace('ipv4', 'ipv6') + logger.debug(f'Setting translated to IPv6, Path: {dotted_settings}.{_key}') + elif isinstance(_val, list): + updated = False + new_list = _val + for i in range(len(new_list)): + if is_ipv4_url(new_list[i]): + new_list[i] = new_list[i].replace('ipv4', 'ipv6') + updated = True + if updated: + data[f'{dotted_settings}.{_key}'] = new_list + logger.debug(f'Setting translated to IPv6, Path: {dotted_settings}.{_key}') + elif isinstance(_val, dict): + new_setting_major = setting_major + [_key] + ipv6_translator(settings_list=_val, setting_major=new_setting_major, data=data) + + +def ipv6_hostname_translation(settings, data): + """Migrates any IPv4 containing hostname in conf to IPv6 hostname""" + settings_path = [] + if settings.server.is_ipv6: + all_settings = settings.loaded_by_loaders.items() + for loader_name, loader_settings in tuple(all_settings): + if loader_name.loader == 'yaml': + ipv6_translator(loader_settings, settings_path, data) + else: + logger.debug('IPv6 Hostname dynaconf migration hook is skipped for IPv4 testing') diff --git a/tests/foreman/installer/test_installer.py b/tests/foreman/installer/test_installer.py index 8332753b162..506dddef312 100644 --- a/tests/foreman/installer/test_installer.py +++ b/tests/foreman/installer/test_installer.py @@ -160,7 +160,9 @@ def sat_default_install(module_sat_ready_rhels): f'foreman-initial-admin-password {settings.server.admin_password}', ] install_satellite(module_sat_ready_rhels[0], installer_args) - return module_sat_ready_rhels[0] + sat = module_sat_ready_rhels[0] + sat.enable_ipv6_http_proxy() + return sat @pytest.fixture(scope='module') @@ -175,10 +177,10 @@ def sat_non_default_install(module_sat_ready_rhels): 'foreman-proxy-plugin-discovery-install-images true', ] install_satellite(module_sat_ready_rhels[1], installer_args, enable_fapolicyd=True) - module_sat_ready_rhels[1].execute( - 'dnf -y --disableplugin=foreman-protector install foreman-discovery-image' - ) - return module_sat_ready_rhels[1] + sat = module_sat_ready_rhels[1] + sat.enable_ipv6_http_proxy() + sat.execute('dnf -y --disableplugin=foreman-protector install foreman-discovery-image') + return sat @pytest.mark.e2e From 32607bab72dc33b3f14203271f4130f777ffd337 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 13 Jun 2024 23:24:22 -0400 Subject: [PATCH 05/13] Bump redis from 5.0.5 to 5.0.6 (#15407) --- requirements-optional.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-optional.txt b/requirements-optional.txt index 2c2c8668b56..04a5a0be59d 100644 --- a/requirements-optional.txt +++ b/requirements-optional.txt @@ -1,7 +1,7 @@ # For running tests and checking code quality using these modules. flake8==7.0.0 pytest-cov==5.0.0 -redis==5.0.5 +redis==5.0.6 pre-commit==3.7.1 # For generating documentation. From dc996ddeb87c2cfba96086166016e432e43b2a22 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 16 Jun 2024 23:39:20 -0400 Subject: [PATCH 06/13] Bump flake8 from 7.0.0 to 7.1.0 (#15417) --- requirements-optional.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-optional.txt b/requirements-optional.txt index 04a5a0be59d..761c47b4b71 100644 --- a/requirements-optional.txt +++ b/requirements-optional.txt @@ -1,5 +1,5 @@ # For running tests and checking code quality using these modules. -flake8==7.0.0 +flake8==7.1.0 pytest-cov==5.0.0 redis==5.0.6 pre-commit==3.7.1 From 90ffb9b671af4a9ae74fedb4f2468df05f4d024f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Gajdu=C5=A1ek?= Date: Mon, 17 Jun 2024 11:14:20 +0200 Subject: [PATCH 07/13] Fix validation: validate IPv6 proxy URL only when IS_IPV6 is set to True (#15413) --- robottelo/config/validators.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/robottelo/config/validators.py b/robottelo/config/validators.py index 1c37d63404f..2ae4c42ac04 100644 --- a/robottelo/config/validators.py +++ b/robottelo/config/validators.py @@ -36,7 +36,12 @@ Validator('server.ssh_password', default=None), Validator('server.verify_ca', default=False), Validator('server.is_ipv6', is_type_of=bool, default=False), - Validator('server.http_proxy_ipv6_url', is_type_of=str, default=None), + # validate http_proxy_ipv6_url only if is_ipv6 is True + Validator( + 'server.http_proxy_ipv6_url', + is_type_of=str, + when=Validator('server.is_ipv6', eq=True), + ), ], content_host=[ Validator('content_host.default_rhel_version', must_exist=True), From ec713355a21061be9ff536b6b0ce6ca36f0836ed Mon Sep 17 00:00:00 2001 From: Gaurav Talreja Date: Mon, 17 Jun 2024 19:43:27 +0530 Subject: [PATCH 08/13] Modify ansible variable test to cover non-admin scenario (#15416) Signed-off-by: Gaurav Talreja --- tests/foreman/ui/test_ansible.py | 70 +++++++++++++++----------------- 1 file changed, 32 insertions(+), 38 deletions(-) diff --git a/tests/foreman/ui/test_ansible.py b/tests/foreman/ui/test_ansible.py index 0b29d2c2c17..23cd7fa6fc9 100644 --- a/tests/foreman/ui/test_ansible.py +++ b/tests/foreman/ui/test_ansible.py @@ -29,40 +29,11 @@ class TestAnsibleCfgMgmt: :CaseComponent: Ansible-ConfigurationManagement """ - @pytest.mark.tier2 - def test_positive_create_and_delete_variable(self, target_sat): - """Create an Ansible variable with the minimum required values, then delete the variable. - - :id: 7006d7c7-788a-4447-a564-d6b03ec06aaf - - :steps: - 1. Import Ansible roles if none have been imported yet. - 2. Create an Ansible variable with only a name and an assigned Ansible role. - 3. Verify that the Ansible variable has been created. - 4. Delete the Ansible variable. - 5. Verify that the Ansible Variable has been deleted. - - :expectedresults: The variable is successfully created and deleted. - """ - key = gen_string('alpha') - SELECTED_ROLE = 'redhat.satellite.activation_keys' - proxy_id = target_sat.nailgun_smart_proxy.id - target_sat.api.AnsibleRoles().sync( - data={'proxy_id': proxy_id, 'role_names': [SELECTED_ROLE]} - ) - with target_sat.ui_session() as session: - session.ansiblevariables.create( - { - 'key': key, - 'ansible_role': SELECTED_ROLE, - } - ) - assert session.ansiblevariables.search(key)[0]['Name'] == key - session.ansiblevariables.delete(key) - assert not session.ansiblevariables.search(key) - @pytest.mark.tier3 - def test_positive_create_variable_with_overrides(self, target_sat): + @pytest.mark.parametrize('auth_type', ['admin', 'non-admin']) + def test_positive_create_delete_variable_with_overrides( + self, request, function_org, target_sat, auth_type + ): """Create an Ansible variable with all values populated. :id: 90acea37-4c2f-42e5-92a6-0c88148f4fb6 @@ -76,19 +47,37 @@ def test_positive_create_variable_with_overrides(self, target_sat): :expectedresults: The variable is successfully created. """ + user_cfg = admin_nailgun_config() + password = settings.server.admin_password key = gen_string('alpha') + param_type = 'integer' + if auth_type == 'non-admin': + ansible_manager_role = target_sat.api.Role().search( + query={'search': 'name="Ansible Roles Manager"'} + ) + user = target_sat.api.User( + role=ansible_manager_role, + admin=False, + login=gen_string('alphanumeric'), + password=password, + organization=[function_org], + ).create() + request.addfinalizer(user.delete) + user_cfg = user_nailgun_config(user.login, password) + SELECTED_ROLE = 'redhat.satellite.activation_keys' proxy_id = target_sat.nailgun_smart_proxy.id - target_sat.api.AnsibleRoles().sync( + target_sat.api.AnsibleRoles(server_config=user_cfg).sync( data={'proxy_id': proxy_id, 'role_names': [SELECTED_ROLE]} ) - with target_sat.ui_session() as session: + with target_sat.ui_session(user=user_cfg.auth[0], password=password) as session: + session.organization.select(function_org.name) session.ansiblevariables.create_with_overrides( { 'key': key, - 'description': 'this is a description', + 'description': gen_string(str_type='alpha'), 'ansible_role': SELECTED_ROLE, - 'parameter_type': 'integer', + 'parameter_type': param_type, 'default_value': '11', 'validator_type': 'list', 'validator_rule': '11, 12, 13', @@ -101,7 +90,12 @@ def test_positive_create_variable_with_overrides(self, target_sat): ], } ) - assert session.ansiblevariables.search(key)[0]['Name'] == key + result = session.ansiblevariables.search(key)[0] + assert result['Name'] == key + assert result['Role'] == SELECTED_ROLE + assert result['Type'] == param_type + assert result['Imported?'] == '' + session.ansiblevariables.delete(key) assert not session.ansiblevariables.search(key) From a41a883fa4f34db6dbe276a0538fab9f753d0678 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 17 Jun 2024 23:32:46 -0400 Subject: [PATCH 09/13] Bump tenacity from 8.3.0 to 8.4.1 (#15434) --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 52afca87ff8..91862b22f9d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -23,7 +23,7 @@ pytest-fixturecollection==0.1.2 pytest-ibutsu==2.2.4 PyYAML==6.0.1 requests==2.32.3 -tenacity==8.3.0 +tenacity==8.4.1 testimony==2.4.0 wait-for==1.2.0 wrapanapi==3.6.0 From 390e7341febfd9c6efb333f9cfc95112c238f866 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 18 Jun 2024 12:52:13 +0530 Subject: [PATCH 10/13] [pre-commit.ci] pre-commit autoupdate (#15432) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.4.8 → v0.4.9](https://github.com/astral-sh/ruff-pre-commit/compare/v0.4.8...v0.4.9) - [github.com/gitleaks/gitleaks: v8.18.3 → v8.18.4](https://github.com/gitleaks/gitleaks/compare/v8.18.3...v8.18.4) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index eaaa473b8db..5630eb82aa1 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -12,7 +12,7 @@ repos: - id: check-yaml - id: debug-statements - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.4.8 + rev: v0.4.9 hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] @@ -28,6 +28,6 @@ repos: types: [text] require_serial: true - repo: https://github.com/gitleaks/gitleaks - rev: v8.18.3 + rev: v8.18.4 hooks: - id: gitleaks From d636d43ece2ff1c3c0a6534f79a1b14174cbc1f4 Mon Sep 17 00:00:00 2001 From: vsedmik <46570670+vsedmik@users.noreply.github.com> Date: Tue, 18 Jun 2024 11:15:15 +0200 Subject: [PATCH 11/13] Update repo checksum with one supported (#15426) * Update repo checksum with one supported * Put checksums in variables --- tests/foreman/api/test_capsulecontent.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/tests/foreman/api/test_capsulecontent.py b/tests/foreman/api/test_capsulecontent.py index 562105856c6..d03f6f3aa24 100644 --- a/tests/foreman/api/test_capsulecontent.py +++ b/tests/foreman/api/test_capsulecontent.py @@ -155,10 +155,12 @@ def test_positive_checksum_sync( :CaseImportance: Critical """ + original_checksum = 'sha256' + new_checksum = 'sha512' # Create repository with sha256 checksum type repo = target_sat.api.Repository( product=function_product, - checksum_type='sha256', + checksum_type=original_checksum, mirroring_policy='additive', download_policy='immediate', ).create() @@ -188,7 +190,7 @@ def test_positive_checksum_sync( cvv = cvv.read() assert len(cvv.environment) == 2 - # Verify repodata's checksum type is sha256, not sha1 on capsule + # Verify repodata's checksum type is sha256, not sha512 on capsule repo_url = module_capsule_configured.get_published_repo_url( org=function_org.label, prod=function_product.label, @@ -198,11 +200,11 @@ def test_positive_checksum_sync( ) repomd = get_repomd(repo_url) checksum_types = re.findall(r'(?<=checksum type=").*?(?=")', repomd) - assert "sha1" not in checksum_types - assert "sha256" in checksum_types + assert new_checksum not in checksum_types + assert original_checksum in checksum_types - # Update repo's checksum type to sha1 - repo.checksum_type = 'sha1' + # Update repo's checksum type to sha512 + repo.checksum_type = new_checksum repo = repo.update(['checksum_type']) # Sync, publish, and promote repo @@ -221,11 +223,11 @@ def test_positive_checksum_sync( cvv = cvv.read() assert len(cvv.environment) == 2 - # Verify repodata's checksum type has updated to sha1 on capsule + # Verify repodata's checksum type has updated to sha512 on capsule repomd = get_repomd(repo_url) checksum_types = re.findall(r'(?<=checksum type=").*?(?=")', repomd) - assert "sha1" in checksum_types - assert "sha256" not in checksum_types + assert new_checksum in checksum_types + assert original_checksum not in checksum_types @pytest.mark.skip_if_open("BZ:2025494") @pytest.mark.e2e From 617286948df092182d64cf87513d8a0d2f447e41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Gajdu=C5=A1ek?= Date: Tue, 18 Jun 2024 12:24:16 +0200 Subject: [PATCH 12/13] Enforce the Dynaconf validaton (#15289) --- .github/workflows/pull_request.yml | 1 + conf/certs.yaml.template | 6 ------ conf/clients.yaml.template | 5 ----- conf/discovery.yaml.template | 3 --- conf/distro.yaml.template | 6 ------ conf/robottelo.yaml.template | 1 + robottelo/config/__init__.py | 8 ++++---- robottelo/config/validators.py | 24 +++--------------------- tests/foreman/api/test_capsulecontent.py | 18 +++++++++--------- tests/foreman/cli/test_activationkey.py | 5 ----- tests/foreman/ui/test_activationkey.py | 7 ------- tests/foreman/ui/test_contenthost.py | 14 +++++++------- tests/foreman/ui/test_dashboard.py | 1 - 13 files changed, 25 insertions(+), 74 deletions(-) delete mode 100644 conf/certs.yaml.template delete mode 100644 conf/clients.yaml.template delete mode 100644 conf/discovery.yaml.template delete mode 100644 conf/distro.yaml.template diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 276ff267e1a..1e65633a285 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -9,6 +9,7 @@ env: PYCURL_SSL_LIBRARY: openssl ROBOTTELO_BUGZILLA__API_KEY: ${{ secrets.BUGZILLA_KEY }} ROBOTTELO_JIRA__API_KEY: ${{ secrets.JIRA_KEY }} + ROBOTTELO_ROBOTTELO__SETTINGS__IGNORE_VALIDATION_ERRORS: true jobs: codechecks: diff --git a/conf/certs.yaml.template b/conf/certs.yaml.template deleted file mode 100644 index e2b86d6d7bd..00000000000 --- a/conf/certs.yaml.template +++ /dev/null @@ -1,6 +0,0 @@ -CERTS: - CERT_FILE: "~/certs/server.crt" - KEY_FILE: "~/certs/server.key" - REQ_FILE: "~/certs/server.csr" - # CA cert (a.k.a cacert.crt or rootCA.pem) can be used as bundle file. - CA_BUNDLE_FILE: "~/certs/rootCA.pem" diff --git a/conf/clients.yaml.template b/conf/clients.yaml.template deleted file mode 100644 index 710a09bb68d..00000000000 --- a/conf/clients.yaml.template +++ /dev/null @@ -1,5 +0,0 @@ -CLIENTS: - # Provisioning server hostname where the clients will be created - PROVISIONING_SERVER: - # Path on the provisioning server where the virtual images will be stored, default "/var/lib/libvirt/images/" - IMAGE_DIR: diff --git a/conf/discovery.yaml.template b/conf/discovery.yaml.template deleted file mode 100644 index a24040aff06..00000000000 --- a/conf/discovery.yaml.template +++ /dev/null @@ -1,3 +0,0 @@ -DISCOVERY: - SATELLITE_VERSION_SHORT: "@jinja {{this.robottelo.satellite_version | replace('.', '')}}" - DISCOVERY_ISO: "discovery_image_sat{this[discovery].satellite_version_short}.iso" diff --git a/conf/distro.yaml.template b/conf/distro.yaml.template deleted file mode 100644 index 061374b4565..00000000000 --- a/conf/distro.yaml.template +++ /dev/null @@ -1,6 +0,0 @@ -DISTRO: - IMAGE_EL6: rhel610 - IMAGE_EL7: rhel79-20200908.1 - IMAGE_EL8: rhel820-20200423.0 - IMAGE_SLES11: sles-11-4 - IMAGE_SLES12: sles-12-3 diff --git a/conf/robottelo.yaml.template b/conf/robottelo.yaml.template index 7bf111cdc65..513ee0db15e 100644 --- a/conf/robottelo.yaml.template +++ b/conf/robottelo.yaml.template @@ -21,3 +21,4 @@ ROBOTTELO: # Dynaconf and Dynaconf hooks related options SETTINGS: GET_FRESH: true + IGNORE_VALIDATION_ERRORS: false diff --git a/robottelo/config/__init__.py b/robottelo/config/__init__.py index d761dd06e91..16af2133b31 100644 --- a/robottelo/config/__init__.py +++ b/robottelo/config/__init__.py @@ -40,10 +40,10 @@ def get_settings(): try: settings.validators.validate() except ValidationError as err: - logger.warning( - f'Dynaconf validation failed, continuing for the sake of unit tests\n{err}' - ) - + if settings.robottelo.settings.get('ignore_validation_errors'): + logger.warning(f'Dynaconf validation failed with\n{err}') + else: + raise err return settings diff --git a/robottelo/config/validators.py b/robottelo/config/validators.py index 2ae4c42ac04..bf3d313b514 100644 --- a/robottelo/config/validators.py +++ b/robottelo/config/validators.py @@ -85,16 +85,6 @@ Validator('capsule.deploy_workflows.os', must_exist=True), Validator('capsule.deploy_arguments', must_exist=True, is_type_of=dict, default={}), ], - certs=[ - Validator( - 'certs.cert_file', - 'certs.key_file', - 'certs.req_file', - 'certs.ca_bundle_file', - must_exist=True, - ) - ], - clients=[Validator('clients.provisioning_server')], libvirt=[ Validator('libvirt.libvirt_hostname', must_exist=True), Validator('libvirt.libvirt_image_dir', default='/var/lib/libvirt/images'), @@ -116,17 +106,6 @@ must_exist=True, ), ], - discovery=[Validator('discovery.discovery_iso', must_exist=True)], - distro=[ - Validator( - 'distro.image_el7', - 'distro.image_el6', - 'distro.image_el8', - 'distro.image_sles11', - 'distro.image_sles12', - must_exist=True, - ) - ], docker=[ Validator( 'docker.external_registry_1', @@ -335,6 +314,9 @@ Validator('remotedb.ssl', default=True), Validator('remotedb.port', default=5432), ], + robottelo=[ + Validator('robottelo.settings.ignore_validation_errors', is_type_of=bool, default=False), + ], shared_function=[ Validator('shared_function.storage', is_in=('file', 'redis'), default='file'), Validator('shared_function.share_timeout', lte=86400, default=86400), diff --git a/tests/foreman/api/test_capsulecontent.py b/tests/foreman/api/test_capsulecontent.py index d03f6f3aa24..e048eecb077 100644 --- a/tests/foreman/api/test_capsulecontent.py +++ b/tests/foreman/api/test_capsulecontent.py @@ -75,7 +75,7 @@ class TestCapsuleContentManagement: """ @pytest.mark.tier4 - @pytest.mark.skip_if_not_set('capsule', 'clients', 'fake_manifest') + @pytest.mark.skip_if_not_set('capsule', 'fake_manifest') def test_positive_uploaded_content_library_sync( self, module_capsule_configured, @@ -136,7 +136,7 @@ def test_positive_uploaded_content_library_sync( assert caps_files[0] == RPM_TO_UPLOAD @pytest.mark.tier4 - @pytest.mark.skip_if_not_set('capsule', 'clients', 'fake_manifest') + @pytest.mark.skip_if_not_set('capsule', 'fake_manifest') def test_positive_checksum_sync( self, module_capsule_configured, function_org, function_product, function_lce, target_sat ): @@ -340,7 +340,7 @@ def test_positive_sync_updated_repo( @pytest.mark.e2e @pytest.mark.tier4 - @pytest.mark.skip_if_not_set('capsule', 'clients', 'fake_manifest') + @pytest.mark.skip_if_not_set('capsule', 'fake_manifest') def test_positive_capsule_sync( self, target_sat, @@ -492,7 +492,7 @@ def test_positive_capsule_sync( assert sat_files == caps_files @pytest.mark.tier4 - @pytest.mark.skip_if_not_set('capsule', 'clients') + @pytest.mark.skip_if_not_set('capsule') def test_positive_iso_library_sync( self, module_capsule_configured, module_sca_manifest_org, module_target_sat ): @@ -559,7 +559,7 @@ def test_positive_iso_library_sync( assert set(sat_isos) == set(caps_isos) @pytest.mark.tier4 - @pytest.mark.skip_if_not_set('capsule', 'clients', 'fake_manifest') + @pytest.mark.skip_if_not_set('capsule', 'fake_manifest') def test_positive_on_demand_sync( self, target_sat, @@ -644,7 +644,7 @@ def test_positive_on_demand_sync( assert package_md5 == published_package_md5 @pytest.mark.tier4 - @pytest.mark.skip_if_not_set('capsule', 'clients', 'fake_manifest') + @pytest.mark.skip_if_not_set('capsule', 'fake_manifest') def test_positive_update_with_immediate_sync( self, target_sat, @@ -746,7 +746,7 @@ def test_positive_update_with_immediate_sync( @pytest.mark.skip_if_open("BZ:2122780") @pytest.mark.tier4 - @pytest.mark.skip_if_not_set('capsule', 'clients', 'fake_manifest') + @pytest.mark.skip_if_not_set('capsule', 'fake_manifest') def test_positive_capsule_pub_url_accessible(self, module_capsule_configured): """Ensure capsule pub url is accessible @@ -770,7 +770,7 @@ def test_positive_capsule_pub_url_accessible(self, module_capsule_configured): @pytest.mark.e2e @pytest.mark.tier4 - @pytest.mark.skip_if_not_set('capsule', 'clients') + @pytest.mark.skip_if_not_set('capsule') @pytest.mark.parametrize('distro', ['rhel7', 'rhel8_bos', 'rhel9_bos']) def test_positive_sync_kickstart_repo( self, target_sat, module_capsule_configured, function_sca_manifest_org, distro @@ -865,7 +865,7 @@ def test_positive_sync_kickstart_repo( @pytest.mark.tier4 @pytest.mark.e2e - @pytest.mark.skip_if_not_set('capsule', 'clients') + @pytest.mark.skip_if_not_set('capsule') def test_positive_sync_container_repo_end_to_end( self, target_sat, diff --git a/tests/foreman/cli/test_activationkey.py b/tests/foreman/cli/test_activationkey.py index 92708960bc5..46743b1a33a 100644 --- a/tests/foreman/cli/test_activationkey.py +++ b/tests/foreman/cli/test_activationkey.py @@ -590,7 +590,6 @@ def test_negative_update_usage_limit(module_org, module_target_sat): assert 'Validation failed: Max hosts must be less than 2147483648' in raise_ctx.value.message -@pytest.mark.skip_if_not_set('clients') @pytest.mark.tier3 @pytest.mark.upgrade def test_positive_usage_limit(module_org, module_location, target_sat): @@ -871,7 +870,6 @@ def test_positive_delete_subscription(function_entitlement_manifest_org, module_ assert subscription_result[-1]['name'] not in ak_subs_info -@pytest.mark.skip_if_not_set('clients') @pytest.mark.tier3 @pytest.mark.upgrade def test_positive_update_aks_to_chost(module_org, module_location, rhel7_contenthost, target_sat): @@ -912,7 +910,6 @@ def test_positive_update_aks_to_chost(module_org, module_location, rhel7_content assert rhel7_contenthost.subscribed -@pytest.mark.skip_if_not_set('clients') @pytest.mark.stubbed @pytest.mark.tier3 def test_positive_update_aks_to_chost_in_one_command(module_org): @@ -1601,7 +1598,6 @@ def test_positive_view_subscriptions_by_non_admin_user( assert subscriptions[0]['id'] == subscription_id -@pytest.mark.skip_if_not_set('clients') @pytest.mark.tier3 def test_positive_subscription_quantity_attached(function_org, rhel7_contenthost, target_sat): """Check the Quantity and Attached fields of 'hammer activation-key subscriptions' @@ -1659,7 +1655,6 @@ def test_positive_subscription_quantity_attached(function_org, rhel7_contenthost assert regex.match(ak_sub['attached']) -@pytest.mark.skip_if_not_set('clients') @pytest.mark.tier3 def test_positive_ak_with_custom_product_on_rhel6( module_org, module_location, rhel6_contenthost, target_sat diff --git a/tests/foreman/ui/test_activationkey.py b/tests/foreman/ui/test_activationkey.py index 067921eda38..f3f87ae8e81 100644 --- a/tests/foreman/ui/test_activationkey.py +++ b/tests/foreman/ui/test_activationkey.py @@ -818,7 +818,6 @@ def test_positive_add_docker_repo_ccv(session, module_org, module_target_sat): assert ak['details']['lce'][lce.name][lce.name] -@pytest.mark.skip_if_not_set('clients') @pytest.mark.tier3 def test_positive_add_host(session, module_org, rhel_contenthost, target_sat): """Test that hosts can be associated to Activation Keys @@ -851,7 +850,6 @@ def test_positive_add_host(session, module_org, rhel_contenthost, target_sat): assert ak['content_hosts']['table'][0]['Name'] == rhel_contenthost.hostname -@pytest.mark.skip_if_not_set('clients') @pytest.mark.tier3 def test_positive_delete_with_system(session, rhel_contenthost, target_sat): """Delete an Activation key which has registered systems @@ -891,7 +889,6 @@ def test_positive_delete_with_system(session, rhel_contenthost, target_sat): assert session.activationkey.search(name)[0]['Name'] != name -@pytest.mark.skip_if_not_set('clients') @pytest.mark.tier3 def test_negative_usage_limit(session, module_org, target_sat): """Test that Usage limit actually limits usage @@ -926,7 +923,6 @@ def test_negative_usage_limit(session, module_org, target_sat): assert f'Max Hosts ({hosts_limit}) reached for activation key' in str(result.stderr) -@pytest.mark.skip_if_not_set('clients') @pytest.mark.tier3 @pytest.mark.upgrade @pytest.mark.skipif((not settings.robottelo.repos_hosting_url), reason='Missing repos_hosting_url') @@ -984,7 +980,6 @@ def test_positive_add_multiple_aks_to_system(session, module_org, rhel_contentho assert ak['content_hosts']['table'][0]['Name'] == rhel_contenthost.hostname -@pytest.mark.skip_if_not_set('clients') @pytest.mark.tier3 @pytest.mark.upgrade @pytest.mark.skipif((not settings.robottelo.REPOS_HOSTING_URL), reason='Missing repos_hosting_url') @@ -1031,7 +1026,6 @@ def test_positive_host_associations(session, target_sat): assert ak2['content_hosts']['table'][0]['Name'] == vm2.hostname -@pytest.mark.skip_if_not_set('clients') @pytest.mark.tier3 @pytest.mark.skipif((not settings.robottelo.repos_hosting_url), reason='Missing repos_hosting_url') def test_positive_service_level_subscription_with_custom_product( @@ -1142,7 +1136,6 @@ def test_positive_delete_manifest(session, function_entitlement_manifest_org, ta @pytest.mark.rhel_ver_list([6]) -@pytest.mark.skip_if_not_set('clients') @pytest.mark.tier3 @pytest.mark.skipif((not settings.robottelo.repos_hosting_url), reason='Missing repos_hosting_url') def test_positive_ak_with_custom_product_on_rhel6(rhel_contenthost, target_sat): diff --git a/tests/foreman/ui/test_contenthost.py b/tests/foreman/ui/test_contenthost.py index a386cc18995..d1cd3c41a2c 100644 --- a/tests/foreman/ui/test_contenthost.py +++ b/tests/foreman/ui/test_contenthost.py @@ -37,7 +37,7 @@ from robottelo.exceptions import CLIFactoryError from robottelo.utils.virtwho import create_fake_hypervisor_content -if not setting_is_set('clients') or not setting_is_set('fake_manifest'): +if not setting_is_set('fake_manifest'): pytest.skip('skipping tests due to missing settings', allow_module_level=True) @@ -1465,7 +1465,7 @@ def test_module_stream_update_from_satellite(session, default_location, vm_modul ) -@pytest.mark.skip_if_not_set('clients', 'fake_manifest') +@pytest.mark.skip_if_not_set('fake_manifest') @pytest.mark.tier3 @pytest.mark.parametrize( 'module_repos_collection_with_manifest', @@ -1506,7 +1506,7 @@ def test_syspurpose_attributes_empty(session, default_location, vm_module_stream assert details[spname] == '' -@pytest.mark.skip_if_not_set('clients', 'fake_manifest') +@pytest.mark.skip_if_not_set('fake_manifest') @pytest.mark.tier3 @pytest.mark.parametrize( 'module_repos_collection_with_manifest', @@ -1550,7 +1550,7 @@ def test_set_syspurpose_attributes_cli(session, default_location, vm_module_stre assert details[spname] == spdata[1] -@pytest.mark.skip_if_not_set('clients', 'fake_manifest') +@pytest.mark.skip_if_not_set('fake_manifest') @pytest.mark.tier3 @pytest.mark.parametrize( 'module_repos_collection_with_manifest', @@ -1598,7 +1598,7 @@ def test_unset_syspurpose_attributes_cli(session, default_location, vm_module_st assert details[spname] == '' -@pytest.mark.skip_if_not_set('clients', 'fake_manifest') +@pytest.mark.skip_if_not_set('fake_manifest') @pytest.mark.tier3 @pytest.mark.parametrize( 'module_repos_collection_with_manifest', @@ -1639,7 +1639,7 @@ def test_syspurpose_matched(session, default_location, vm_module_streams): assert details['system_purpose_status'] == 'Matched' -@pytest.mark.skip_if_not_set('clients', 'fake_manifest') +@pytest.mark.skip_if_not_set('fake_manifest') @pytest.mark.tier3 @pytest.mark.parametrize( 'module_repos_collection_with_manifest', @@ -1683,7 +1683,7 @@ def test_syspurpose_bulk_action(session, default_location, vm): assert val in result.stdout -@pytest.mark.skip_if_not_set('clients', 'fake_manifest') +@pytest.mark.skip_if_not_set('fake_manifest') @pytest.mark.tier3 @pytest.mark.parametrize( 'module_repos_collection_with_manifest', diff --git a/tests/foreman/ui/test_dashboard.py b/tests/foreman/ui/test_dashboard.py index 071e44cc4d7..d3f3439981f 100644 --- a/tests/foreman/ui/test_dashboard.py +++ b/tests/foreman/ui/test_dashboard.py @@ -176,7 +176,6 @@ def test_positive_task_status(session, target_sat): @pytest.mark.upgrade @pytest.mark.no_containers @pytest.mark.run_in_one_thread -@pytest.mark.skip_if_not_set('clients') @pytest.mark.tier3 @pytest.mark.rhel_ver_match('8') @pytest.mark.skipif((not settings.robottelo.repos_hosting_url), reason='Missing repos_hosting_url') From 2577467f03555a21aeb76920b8759adcb8c308ef Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 18 Jun 2024 18:22:30 +0530 Subject: [PATCH 13/13] Bump python-box from 7.1.1 to 7.2.0 (#15399) Bumps [python-box](https://github.com/cdgriffith/Box) from 7.1.1 to 7.2.0. - [Release notes](https://github.com/cdgriffith/Box/releases) - [Changelog](https://github.com/cdgriffith/Box/blob/master/CHANGES.rst) - [Commits](https://github.com/cdgriffith/Box/compare/7.1.1...7.2.0) --- updated-dependencies: - dependency-name: python-box dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 91862b22f9d..3c85cab4278 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,7 +12,7 @@ manifester==0.2.4 navmazing==1.2.2 productmd==1.38 pyotp==2.9.0 -python-box==7.1.1 +python-box==7.2.0 pytest==8.2.2 pytest-order==1.2.1 pytest-services==2.2.1